Add tests, mocks and lint
This commit is contained in:
parent
7d95feadc1
commit
ccd684ea40
24 changed files with 523 additions and 96 deletions
|
|
@ -41,6 +41,7 @@ dependencies {
|
||||||
implementation(projects.libraries.featureflag.ui)
|
implementation(projects.libraries.featureflag.ui)
|
||||||
implementation(projects.libraries.network)
|
implementation(projects.libraries.network)
|
||||||
implementation(projects.libraries.pushstore.api)
|
implementation(projects.libraries.pushstore.api)
|
||||||
|
implementation(projects.libraries.pushstore.test)
|
||||||
implementation(projects.libraries.testtags)
|
implementation(projects.libraries.testtags)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.features.rageshake.api)
|
implementation(projects.features.rageshake.api)
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ import io.element.android.libraries.architecture.BackstackNode
|
||||||
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,12 @@
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.notifications
|
package io.element.android.features.preferences.impl.notifications
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
|
||||||
|
|
||||||
sealed interface NotificationSettingsEvents {
|
sealed interface NotificationSettingsEvents {
|
||||||
|
|
||||||
data object RefreshSystemNotificationsEnabled : NotificationSettingsEvents
|
data object RefreshSystemNotificationsEnabled : NotificationSettingsEvents
|
||||||
data class SetNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
|
data class SetNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
|
||||||
data class SetAtRoomNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
|
data class SetAtRoomNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
|
||||||
data class SetCallNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
|
data class SetCallNotificationsEnabled(val enabled: Boolean) : NotificationSettingsEvents
|
||||||
data class SetDefaultGroupNotificationMode(val mode: RoomNotificationMode) : NotificationSettingsEvents
|
|
||||||
data class SetDefaultOneToOneNotificationMode(val mode: RoomNotificationMode) : NotificationSettingsEvents
|
|
||||||
data object FixConfigurationMismatch : NotificationSettingsEvents
|
data object FixConfigurationMismatch : NotificationSettingsEvents
|
||||||
data object ClearConfigurationMismatchError : NotificationSettingsEvents
|
data object ClearConfigurationMismatchError : NotificationSettingsEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
class NotificationSettingsNode @AssistedInject constructor(
|
class NotificationSettingsNode @AssistedInject constructor(
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
.getNotificationEnabledForDevice()
|
.getNotificationEnabledForDevice()
|
||||||
.collectAsState(initial = false)
|
.collectAsState(initial = false)
|
||||||
|
|
||||||
val matrixSettings: MutableState<NotificationSettingsState.MatrixNotificationSettings> = remember {
|
val matrixSettings: MutableState<NotificationSettingsState.MatrixSettings> = remember {
|
||||||
mutableStateOf(NotificationSettingsState.MatrixNotificationSettings.Uninitialized)
|
mutableStateOf(NotificationSettingsState.MatrixSettings.Uninitialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
@ -69,18 +69,20 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
when (event) {
|
when (event) {
|
||||||
is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> localCoroutineScope.setAtRoomNotificationsEnabled(event.enabled)
|
is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> localCoroutineScope.setAtRoomNotificationsEnabled(event.enabled)
|
||||||
is NotificationSettingsEvents.SetCallNotificationsEnabled -> localCoroutineScope.setCallNotificationsEnabled(event.enabled)
|
is NotificationSettingsEvents.SetCallNotificationsEnabled -> localCoroutineScope.setCallNotificationsEnabled(event.enabled)
|
||||||
is NotificationSettingsEvents.SetDefaultGroupNotificationMode -> localCoroutineScope.setDefaultGroupNotificationMode(event.mode)
|
|
||||||
is NotificationSettingsEvents.SetDefaultOneToOneNotificationMode -> localCoroutineScope.setDefaultOneToOneNotificationMode(event.mode)
|
|
||||||
is NotificationSettingsEvents.SetNotificationsEnabled -> localCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled)
|
is NotificationSettingsEvents.SetNotificationsEnabled -> localCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled)
|
||||||
NotificationSettingsEvents.ClearConfigurationMismatchError -> matrixSettings.value = NotificationSettingsState.MatrixNotificationSettings.InvalidNotificationSettingsState(fixFailed = false)
|
NotificationSettingsEvents.ClearConfigurationMismatchError -> {
|
||||||
|
matrixSettings.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false)
|
||||||
|
}
|
||||||
NotificationSettingsEvents.FixConfigurationMismatch -> localCoroutineScope.fixConfigurationMismatch(matrixSettings)
|
NotificationSettingsEvents.FixConfigurationMismatch -> localCoroutineScope.fixConfigurationMismatch(matrixSettings)
|
||||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled()
|
NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> {
|
||||||
|
systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotificationSettingsState(
|
return NotificationSettingsState(
|
||||||
matrixNotificationSettings = matrixSettings.value,
|
matrixSettings = matrixSettings.value,
|
||||||
appNotificationSettings = NotificationSettingsState.AppNotificationSettings(
|
appSettings = NotificationSettingsState.AppSettings(
|
||||||
systemNotificationsEnabled = systemNotificationsEnabled.value,
|
systemNotificationsEnabled = systemNotificationsEnabled.value,
|
||||||
appNotificationsEnabled = appNotificationsEnabled.value
|
appNotificationsEnabled = appNotificationsEnabled.value
|
||||||
),
|
),
|
||||||
|
|
@ -89,7 +91,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
private fun CoroutineScope.observeNotificationSettings(target: MutableState<NotificationSettingsState.MatrixNotificationSettings>) {
|
private fun CoroutineScope.observeNotificationSettings(target: MutableState<NotificationSettingsState.MatrixSettings>) {
|
||||||
notificationSettingsService.notificationSettingsChangeFlow
|
notificationSettingsService.notificationSettingsChangeFlow
|
||||||
.debounce(0.5.seconds)
|
.debounce(0.5.seconds)
|
||||||
.onEach {
|
.onEach {
|
||||||
|
|
@ -98,7 +100,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.fetchSettings(target: MutableState<NotificationSettingsState.MatrixNotificationSettings>) = launch {
|
private fun CoroutineScope.fetchSettings(target: MutableState<NotificationSettingsState.MatrixSettings>) = launch {
|
||||||
val groupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false).getOrThrow()
|
val groupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false).getOrThrow()
|
||||||
val encryptedGroupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false).getOrThrow()
|
val encryptedGroupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false).getOrThrow()
|
||||||
|
|
||||||
|
|
@ -106,14 +108,14 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
val encryptedOneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = true).getOrThrow()
|
val encryptedOneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = true).getOrThrow()
|
||||||
|
|
||||||
if(groupDefaultMode != encryptedGroupDefaultMode || oneToOneDefaultMode != encryptedOneToOneDefaultMode) {
|
if(groupDefaultMode != encryptedGroupDefaultMode || oneToOneDefaultMode != encryptedOneToOneDefaultMode) {
|
||||||
target.value = NotificationSettingsState.MatrixNotificationSettings.InvalidNotificationSettingsState(fixFailed = false)
|
target.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val callNotificationsEnabled = notificationSettingsService.isCallEnabled().getOrThrow()
|
val callNotificationsEnabled = notificationSettingsService.isCallEnabled().getOrThrow()
|
||||||
val atRoomNotificationsEnabled = notificationSettingsService.isRoomMentionEnabled().getOrThrow()
|
val atRoomNotificationsEnabled = notificationSettingsService.isRoomMentionEnabled().getOrThrow()
|
||||||
|
|
||||||
target.value = NotificationSettingsState.MatrixNotificationSettings.ValidNotificationSettingsState(
|
target.value = NotificationSettingsState.MatrixSettings.Valid(
|
||||||
atRoomNotificationsEnabled = atRoomNotificationsEnabled,
|
atRoomNotificationsEnabled = atRoomNotificationsEnabled,
|
||||||
callNotificationsEnabled = callNotificationsEnabled,
|
callNotificationsEnabled = callNotificationsEnabled,
|
||||||
defaultGroupNotificationMode = encryptedGroupDefaultMode,
|
defaultGroupNotificationMode = encryptedGroupDefaultMode,
|
||||||
|
|
@ -121,7 +123,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.fixConfigurationMismatch(target: MutableState<NotificationSettingsState.MatrixNotificationSettings>) = launch {
|
private fun CoroutineScope.fixConfigurationMismatch(target: MutableState<NotificationSettingsState.MatrixSettings>) = launch {
|
||||||
runCatching {
|
runCatching {
|
||||||
val groupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false).getOrThrow()
|
val groupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false).getOrThrow()
|
||||||
val encryptedGroupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false).getOrThrow()
|
val encryptedGroupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false).getOrThrow()
|
||||||
|
|
@ -147,7 +149,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
}.fold(
|
}.fold(
|
||||||
onSuccess = {},
|
onSuccess = {},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
target.value = NotificationSettingsState.MatrixNotificationSettings.InvalidNotificationSettingsState(fixFailed = true)
|
target.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = true)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -160,27 +162,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
notificationSettingsService.setCallEnabled(enabled)
|
notificationSettingsService.setCallEnabled(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.setDefaultGroupNotificationMode(mode: RoomNotificationMode) = launch {
|
|
||||||
notificationSettingsService.setDefaultRoomNotificationMode(false, mode, false)
|
|
||||||
notificationSettingsService.setDefaultRoomNotificationMode(true, mode, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CoroutineScope.setDefaultOneToOneNotificationMode(mode: RoomNotificationMode) = launch {
|
|
||||||
notificationSettingsService.setDefaultRoomNotificationMode(false, mode, true)
|
|
||||||
notificationSettingsService.setDefaultRoomNotificationMode(true, mode, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CoroutineScope.setNotificationsEnabled(userPushStore: UserPushStore, enabled: Boolean) = launch {
|
private fun CoroutineScope.setNotificationsEnabled(userPushStore: UserPushStore, enabled: Boolean) = launch {
|
||||||
userPushStore.setNotificationEnabledForDevice(enabled)
|
userPushStore.setNotificationEnabledForDevice(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.getDefaultRoomNotificationMode(isOneToOne: Boolean, defaultRoomNotificationMode: MutableState<RoomNotificationMode?>) = launch {
|
|
||||||
val encryptedMode = notificationSettingsService.getDefaultRoomNotificationMode(true, isOneToOne).getOrThrow()
|
|
||||||
val unencryptedMode = notificationSettingsService.getDefaultRoomNotificationMode(false, isOneToOne).getOrThrow()
|
|
||||||
if (encryptedMode == unencryptedMode) {
|
|
||||||
defaultRoomNotificationMode.value
|
|
||||||
} else {
|
|
||||||
defaultRoomNotificationMode.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,25 +21,25 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class NotificationSettingsState(
|
data class NotificationSettingsState(
|
||||||
val matrixNotificationSettings: MatrixNotificationSettings,
|
val matrixSettings: MatrixSettings,
|
||||||
val appNotificationSettings: AppNotificationSettings,
|
val appSettings: AppSettings,
|
||||||
val eventSink: (NotificationSettingsEvents) -> Unit,
|
val eventSink: (NotificationSettingsEvents) -> Unit,
|
||||||
) {
|
) {
|
||||||
sealed interface MatrixNotificationSettings {
|
sealed interface MatrixSettings {
|
||||||
data object Uninitialized : MatrixNotificationSettings
|
data object Uninitialized : MatrixSettings
|
||||||
data class ValidNotificationSettingsState(
|
data class Valid(
|
||||||
val atRoomNotificationsEnabled: Boolean,
|
val atRoomNotificationsEnabled: Boolean,
|
||||||
val callNotificationsEnabled: Boolean,
|
val callNotificationsEnabled: Boolean,
|
||||||
val defaultGroupNotificationMode: RoomNotificationMode?,
|
val defaultGroupNotificationMode: RoomNotificationMode?,
|
||||||
val defaultOneToOneNotificationMode: RoomNotificationMode?,
|
val defaultOneToOneNotificationMode: RoomNotificationMode?,
|
||||||
) : MatrixNotificationSettings
|
) : MatrixSettings
|
||||||
|
|
||||||
data class InvalidNotificationSettingsState(
|
data class Invalid(
|
||||||
val fixFailed: Boolean
|
val fixFailed: Boolean
|
||||||
) : MatrixNotificationSettings
|
) : MatrixSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AppNotificationSettings(
|
data class AppSettings(
|
||||||
val systemNotificationsEnabled: Boolean,
|
val systemNotificationsEnabled: Boolean,
|
||||||
val appNotificationsEnabled: Boolean,
|
val appNotificationsEnabled: Boolean,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,13 @@ open class NotificationSettingsStateProvider : PreviewParameterProvider<Notifica
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aNotificationSettingsState() = NotificationSettingsState(
|
fun aNotificationSettingsState() = NotificationSettingsState(
|
||||||
matrixNotificationSettings = NotificationSettingsState.MatrixNotificationSettings.ValidNotificationSettingsState(
|
matrixSettings = NotificationSettingsState.MatrixSettings.Valid(
|
||||||
atRoomNotificationsEnabled = true,
|
atRoomNotificationsEnabled = true,
|
||||||
callNotificationsEnabled = true,
|
callNotificationsEnabled = true,
|
||||||
defaultGroupNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
defaultGroupNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||||
defaultOneToOneNotificationMode = RoomNotificationMode.ALL_MESSAGES,
|
defaultOneToOneNotificationMode = RoomNotificationMode.ALL_MESSAGES,
|
||||||
),
|
),
|
||||||
appNotificationSettings = NotificationSettingsState.AppNotificationSettings(
|
appSettings = NotificationSettingsState.AppSettings(
|
||||||
systemNotificationsEnabled = false,
|
systemNotificationsEnabled = false,
|
||||||
appNotificationsEnabled = true,
|
appNotificationsEnabled = true,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -74,23 +74,21 @@ fun NotificationSettingsView(
|
||||||
title = stringResource(id = CommonStrings.screen_notification_settings_title)
|
title = stringResource(id = CommonStrings.screen_notification_settings_title)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
when (state.matrixNotificationSettings) {
|
when (state.matrixSettings) {
|
||||||
is NotificationSettingsState.MatrixNotificationSettings.InvalidNotificationSettingsState -> InvalidNotificationSettingsView(
|
is NotificationSettingsState.MatrixSettings.Invalid -> InvalidNotificationSettingsView(
|
||||||
showError = state.matrixNotificationSettings.fixFailed,
|
showError = state.matrixSettings.fixFailed,
|
||||||
onContinueClicked = { state.eventSink(NotificationSettingsEvents.FixConfigurationMismatch) },
|
onContinueClicked = { state.eventSink(NotificationSettingsEvents.FixConfigurationMismatch) },
|
||||||
onDismissError = { state.eventSink(NotificationSettingsEvents.ClearConfigurationMismatchError) },
|
onDismissError = { state.eventSink(NotificationSettingsEvents.ClearConfigurationMismatchError) },
|
||||||
modifier = modifier,
|
|
||||||
)
|
)
|
||||||
NotificationSettingsState.MatrixNotificationSettings.Uninitialized -> return@PreferenceView
|
NotificationSettingsState.MatrixSettings.Uninitialized -> return@PreferenceView
|
||||||
is NotificationSettingsState.MatrixNotificationSettings.ValidNotificationSettingsState -> NotificationSettingsContentView(
|
is NotificationSettingsState.MatrixSettings.Valid -> NotificationSettingsContentView(
|
||||||
matrixSettings = state.matrixNotificationSettings,
|
matrixSettings = state.matrixSettings,
|
||||||
systemSettings = state.appNotificationSettings,
|
systemSettings = state.appSettings,
|
||||||
onNotificationsEnabledChanged = { state.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(it))},
|
onNotificationsEnabledChanged = { state.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(it))},
|
||||||
onGroupChatsClicked = { onOpenEditDefault(false) },
|
onGroupChatsClicked = { onOpenEditDefault(false) },
|
||||||
onDirectChatsClicked = { onOpenEditDefault(true) },
|
onDirectChatsClicked = { onOpenEditDefault(true) },
|
||||||
onMentionNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(it)) },
|
onMentionNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(it)) },
|
||||||
onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) },
|
onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) },
|
||||||
modifier = modifier,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,8 +96,8 @@ fun NotificationSettingsView(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NotificationSettingsContentView(
|
private fun NotificationSettingsContentView(
|
||||||
matrixSettings: NotificationSettingsState.MatrixNotificationSettings.ValidNotificationSettingsState,
|
matrixSettings: NotificationSettingsState.MatrixSettings.Valid,
|
||||||
systemSettings: NotificationSettingsState.AppNotificationSettings,
|
systemSettings: NotificationSettingsState.AppSettings,
|
||||||
onNotificationsEnabledChanged: (Boolean) -> Unit,
|
onNotificationsEnabledChanged: (Boolean) -> Unit,
|
||||||
onGroupChatsClicked: () -> Unit,
|
onGroupChatsClicked: () -> Unit,
|
||||||
onDirectChatsClicked: () -> Unit,
|
onDirectChatsClicked: () -> Unit,
|
||||||
|
|
@ -252,16 +250,16 @@ private fun ContentToPreview(state: NotificationSettingsState) {
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
internal fun InvalidNotificationSettingsViewightPreview(@PreviewParameter(NotificationSettingsStateProvider::class) state: NotificationSettingsState) =
|
internal fun InvalidNotificationSettingsViewightPreview() =
|
||||||
ElementPreviewLight { InvalidNotificationSettingsContentToPreview(state) }
|
ElementPreviewLight { InvalidNotificationSettingsContentToPreview() }
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
internal fun InvalidNotificationSettingsViewDarkPreview(@PreviewParameter(NotificationSettingsStateProvider::class) state: NotificationSettingsState) =
|
internal fun InvalidNotificationSettingsViewDarkPreview() =
|
||||||
ElementPreviewDark { InvalidNotificationSettingsContentToPreview(state) }
|
ElementPreviewDark { InvalidNotificationSettingsContentToPreview() }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InvalidNotificationSettingsContentToPreview(state: NotificationSettingsState) {
|
private fun InvalidNotificationSettingsContentToPreview() {
|
||||||
InvalidNotificationSettingsView(
|
InvalidNotificationSettingsView(
|
||||||
showError = false,
|
showError = false,
|
||||||
onContinueClicked = {},
|
onContinueClicked = {},
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ fun EditDefaultNotificationSettingView(
|
||||||
PreferenceCategory(title = stringResource(id = categoryTitle)) {
|
PreferenceCategory(title = stringResource(id = categoryTitle)) {
|
||||||
|
|
||||||
if (state.mode != null) {
|
if (state.mode != null) {
|
||||||
Column(modifier = modifier.selectableGroup()) {
|
Column(modifier = Modifier.selectableGroup()) {
|
||||||
validModes.forEach { item ->
|
validModes.forEach { item ->
|
||||||
DefaultNotificationSettingOption(
|
DefaultNotificationSettingOption(
|
||||||
mode = item,
|
mode = item,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.preferences.impl.notifications
|
||||||
|
|
||||||
|
import app.cash.molecule.RecompositionMode
|
||||||
|
import app.cash.molecule.moleculeFlow
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import com.google.common.truth.Truth
|
||||||
|
import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingPresenter
|
||||||
|
import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingStateEvents
|
||||||
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||||
|
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class EditDefaultNotificationSettingsPresenterTests {
|
||||||
|
@Test
|
||||||
|
fun `present - ensures initial state is correct`() = runTest {
|
||||||
|
val notificationSettingsService = FakeNotificationSettingsService()
|
||||||
|
val presenter = EditDefaultNotificationSettingPresenter(notificationSettingsService = notificationSettingsService, isOneToOne = false)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
Truth.assertThat(initialState.mode).isNull()
|
||||||
|
Truth.assertThat(initialState.isOneToOne).isFalse()
|
||||||
|
|
||||||
|
val loadedState = consumeItemsUntilPredicate {
|
||||||
|
it.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||||
|
}.last()
|
||||||
|
Truth.assertThat(loadedState.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - edit default notification setting`() = runTest {
|
||||||
|
val notificationSettingsService = FakeNotificationSettingsService()
|
||||||
|
val presenter = EditDefaultNotificationSettingPresenter(notificationSettingsService = notificationSettingsService, isOneToOne = false)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
awaitItem().eventSink(EditDefaultNotificationSettingStateEvents.SetNotificationMode(RoomNotificationMode.ALL_MESSAGES))
|
||||||
|
val loadedState = consumeItemsUntilPredicate {
|
||||||
|
it.mode == RoomNotificationMode.ALL_MESSAGES
|
||||||
|
}.last()
|
||||||
|
Truth.assertThat(loadedState.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.preferences.impl.notifications
|
||||||
|
|
||||||
|
class FakeSystemNotificationsEnabledProvider: SystemNotificationsEnabledProvider {
|
||||||
|
override fun notificationsEnabled(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.preferences.impl.notifications
|
||||||
|
|
||||||
|
import app.cash.molecule.RecompositionMode
|
||||||
|
import app.cash.molecule.moleculeFlow
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import com.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||||
|
import com.google.common.truth.Truth
|
||||||
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
|
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||||
|
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
class NotificationSettingsPresenterTests {
|
||||||
|
@Test
|
||||||
|
fun `present - ensures initial state is correct`() = runTest {
|
||||||
|
val presenter = aNotificationPresenter()
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
Truth.assertThat(initialState.appSettings.appNotificationsEnabled).isFalse()
|
||||||
|
Truth.assertThat(initialState.appSettings.systemNotificationsEnabled).isTrue()
|
||||||
|
Truth.assertThat(initialState.matrixSettings).isEqualTo(NotificationSettingsState.MatrixSettings.Uninitialized)
|
||||||
|
|
||||||
|
val loadedState = consumeItemsUntilPredicate {
|
||||||
|
it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid
|
||||||
|
}.last()
|
||||||
|
Truth.assertThat(loadedState.appSettings.appNotificationsEnabled).isTrue()
|
||||||
|
Truth.assertThat(loadedState.appSettings.systemNotificationsEnabled).isTrue()
|
||||||
|
val valid = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
|
||||||
|
Truth.assertThat(valid?.atRoomNotificationsEnabled).isFalse()
|
||||||
|
Truth.assertThat(valid?.callNotificationsEnabled).isFalse()
|
||||||
|
Truth.assertThat(valid?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
|
||||||
|
Truth.assertThat(valid?.defaultOneToOneNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - default group notification mode changed`() = runTest {
|
||||||
|
val notificationSettingsService = FakeNotificationSettingsService()
|
||||||
|
val presenter = aNotificationPresenter(notificationSettingsService)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
|
||||||
|
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false, mode = RoomNotificationMode.ALL_MESSAGES)
|
||||||
|
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false, mode = RoomNotificationMode.ALL_MESSAGES)
|
||||||
|
val updatedState = consumeItemsUntilPredicate {
|
||||||
|
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)
|
||||||
|
?.defaultGroupNotificationMode == RoomNotificationMode.ALL_MESSAGES
|
||||||
|
}.last()
|
||||||
|
val valid = updatedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
|
||||||
|
Truth.assertThat(valid?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - notification settings mismatched`() = runTest {
|
||||||
|
val notificationSettingsService = FakeNotificationSettingsService()
|
||||||
|
val presenter = aNotificationPresenter(notificationSettingsService)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
|
||||||
|
notificationSettingsService.setDefaultRoomNotificationMode(
|
||||||
|
isEncrypted = true,
|
||||||
|
isOneToOne = false,
|
||||||
|
mode = RoomNotificationMode.ALL_MESSAGES
|
||||||
|
)
|
||||||
|
notificationSettingsService.setDefaultRoomNotificationMode(
|
||||||
|
isEncrypted = false,
|
||||||
|
isOneToOne = false,
|
||||||
|
mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||||
|
)
|
||||||
|
val updatedState = consumeItemsUntilPredicate {
|
||||||
|
it.matrixSettings is NotificationSettingsState.MatrixSettings.Invalid
|
||||||
|
}.last()
|
||||||
|
Truth.assertThat(updatedState.matrixSettings).isEqualTo(NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - fix notification settings mismatched`() = runTest {
|
||||||
|
// Start with a mismatched configuration
|
||||||
|
val notificationSettingsService = FakeNotificationSettingsService(
|
||||||
|
initialEncryptedGroupDefaultMode = RoomNotificationMode.ALL_MESSAGES,
|
||||||
|
initialGroupDefaultMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||||
|
initialEncryptedOneToOneDefaultMode = RoomNotificationMode.ALL_MESSAGES,
|
||||||
|
initialOneToOneDefaultMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||||
|
)
|
||||||
|
val presenter = aNotificationPresenter(notificationSettingsService)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(NotificationSettingsEvents.FixConfigurationMismatch)
|
||||||
|
val fixedState = consumeItemsUntilPredicate(timeout = 2000.milliseconds) {
|
||||||
|
it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid
|
||||||
|
}.last()
|
||||||
|
|
||||||
|
val fixedMatrixState = fixedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
|
||||||
|
Truth.assertThat(fixedMatrixState?.defaultGroupNotificationMode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - set notifications enabled`() = runTest {
|
||||||
|
val presenter = aNotificationPresenter()
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val loadedState = consumeItemsUntilPredicate {
|
||||||
|
it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid
|
||||||
|
}.last()
|
||||||
|
Truth.assertThat(loadedState.appSettings.appNotificationsEnabled).isTrue()
|
||||||
|
|
||||||
|
loadedState.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(false))
|
||||||
|
val updatedState = consumeItemsUntilPredicate {
|
||||||
|
!it.appSettings.appNotificationsEnabled
|
||||||
|
}.last()
|
||||||
|
Truth.assertThat(updatedState.appSettings.appNotificationsEnabled).isFalse()
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - set call notifications enabled`() = runTest {
|
||||||
|
val presenter = aNotificationPresenter()
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val loadedState = consumeItemsUntilPredicate {
|
||||||
|
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.callNotificationsEnabled == false
|
||||||
|
}.last()
|
||||||
|
val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
|
||||||
|
Truth.assertThat(validMatrixState?.callNotificationsEnabled).isFalse()
|
||||||
|
|
||||||
|
loadedState.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(true))
|
||||||
|
val updatedState = consumeItemsUntilPredicate {
|
||||||
|
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.callNotificationsEnabled == true
|
||||||
|
}.last()
|
||||||
|
val updatedMatrixState = updatedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
|
||||||
|
Truth.assertThat(updatedMatrixState?.callNotificationsEnabled).isTrue()
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - set atRoom notifications enabled`() = runTest {
|
||||||
|
val presenter = aNotificationPresenter()
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val loadedState = consumeItemsUntilPredicate {
|
||||||
|
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == false
|
||||||
|
}.last()
|
||||||
|
val validMatrixState = loadedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
|
||||||
|
Truth.assertThat(validMatrixState?.atRoomNotificationsEnabled).isFalse()
|
||||||
|
|
||||||
|
loadedState.eventSink(NotificationSettingsEvents.SetAtRoomNotificationsEnabled(true))
|
||||||
|
val updatedState = consumeItemsUntilPredicate {
|
||||||
|
(it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == true
|
||||||
|
}.last()
|
||||||
|
val updatedMatrixState = updatedState.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid
|
||||||
|
Truth.assertThat(updatedMatrixState?.atRoomNotificationsEnabled).isTrue()
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun aNotificationPresenter(
|
||||||
|
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService()
|
||||||
|
) : NotificationSettingsPresenter {
|
||||||
|
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
|
||||||
|
return NotificationSettingsPresenter(
|
||||||
|
notificationSettingsService = notificationSettingsService,
|
||||||
|
userPushStoreFactory = FakeUserPushStoreFactory(),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
systemNotificationsEnabledProvider = FakeSystemNotificationsEnabledProvider(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ import io.element.android.features.logout.impl.DefaultLogoutPreferencePresenter
|
||||||
import io.element.android.libraries.architecture.Async
|
import io.element.android.libraries.architecture.Async
|
||||||
import io.element.android.libraries.core.meta.BuildType
|
import io.element.android.libraries.core.meta.BuildType
|
||||||
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
|
||||||
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||||
|
|
@ -53,6 +54,7 @@ class PreferencesRootPresenterTest {
|
||||||
BuildType.DEBUG,
|
BuildType.DEBUG,
|
||||||
FakeVersionFormatter(),
|
FakeVersionFormatter(),
|
||||||
SnackbarDispatcher(),
|
SnackbarDispatcher(),
|
||||||
|
FakeFeatureFlagService()
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -385,7 +385,7 @@ class RoomDetailsPresenterTests {
|
||||||
@Test
|
@Test
|
||||||
fun `present - mute room notifications`() = runTest {
|
fun `present - mute room notifications`() = runTest {
|
||||||
val leaveRoomPresenter = LeaveRoomPresenterFake()
|
val leaveRoomPresenter = LeaveRoomPresenterFake()
|
||||||
val notificationSettingsService = FakeNotificationSettingsService(initialMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
|
val notificationSettingsService = FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
|
||||||
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
|
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
|
||||||
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
|
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -404,8 +404,8 @@ class RoomDetailsPresenterTests {
|
||||||
fun `present - unmute room notifications`() = runTest {
|
fun `present - unmute room notifications`() = runTest {
|
||||||
val leaveRoomPresenter = LeaveRoomPresenterFake()
|
val leaveRoomPresenter = LeaveRoomPresenterFake()
|
||||||
val notificationSettingsService = FakeNotificationSettingsService(
|
val notificationSettingsService = FakeNotificationSettingsService(
|
||||||
initialMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||||
initialDefaultMode = RoomNotificationMode.ALL_MESSAGES
|
initialEncryptedGroupDefaultMode = RoomNotificationMode.ALL_MESSAGES
|
||||||
)
|
)
|
||||||
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
|
val room = aMatrixRoom(notificationSettingsService = notificationSettingsService)
|
||||||
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
|
val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import io.element.android.features.roomdetails.aMatrixRoom
|
||||||
import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsEvents
|
import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsEvents
|
||||||
import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsPresenter
|
import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsPresenter
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_NOTIFICATION_MODE
|
|
||||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -67,9 +66,9 @@ class RoomNotificationSettingsPresenterTests {
|
||||||
initialState.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
|
initialState.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
|
||||||
initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true))
|
initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true))
|
||||||
val defaultState = consumeItemsUntilPredicate {
|
val defaultState = consumeItemsUntilPredicate {
|
||||||
it.roomNotificationSettings?.mode == A_ROOM_NOTIFICATION_MODE
|
it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||||
}.last()
|
}.last()
|
||||||
Truth.assertThat(defaultState.roomNotificationSettings?.mode).isEqualTo(A_ROOM_NOTIFICATION_MODE)
|
Truth.assertThat(defaultState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomNotificationSettin
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
interface NotificationSettingsService {
|
interface NotificationSettingsService {
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,11 @@ class RustNotificationSettingsService(
|
||||||
notificationSettings.getDefaultRoomNotificationMode(isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::mapMode)
|
notificationSettings.getDefaultRoomNotificationMode(isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::mapMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setDefaultRoomNotificationMode(isEncrypted: Boolean, mode: RoomNotificationMode, isOneToOne: Boolean): Result<Unit> = withContext(dispatchers.io) {
|
override suspend fun setDefaultRoomNotificationMode(
|
||||||
|
isEncrypted: Boolean,
|
||||||
|
mode: RoomNotificationMode,
|
||||||
|
isOneToOne: Boolean
|
||||||
|
): Result<Unit> = withContext(dispatchers.io) {
|
||||||
runCatching {
|
runCatching {
|
||||||
notificationSettings.setDefaultRoomNotificationMode(isEncrypted, isOneToOne, mode.let(RoomNotificationSettingsMapper::mapMode))
|
notificationSettings.setDefaultRoomNotificationMode(isEncrypted, isOneToOne, mode.let(RoomNotificationSettingsMapper::mapMode))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,32 +25,75 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
|
||||||
class FakeNotificationSettingsService(
|
class FakeNotificationSettingsService(
|
||||||
initialMode: RoomNotificationMode = A_ROOM_NOTIFICATION_MODE,
|
initialRoomMode: RoomNotificationMode = A_ROOM_NOTIFICATION_MODE,
|
||||||
initialDefaultMode: RoomNotificationMode = A_ROOM_NOTIFICATION_MODE
|
initialGroupDefaultMode: RoomNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||||
|
initialEncryptedGroupDefaultMode: RoomNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
|
||||||
|
initialOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES,
|
||||||
|
initialEncryptedOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES,
|
||||||
) : NotificationSettingsService {
|
) : NotificationSettingsService {
|
||||||
private var _roomNotificationSettingsStateFlow = MutableStateFlow(Unit)
|
private var _notificationSettingsStateFlow = MutableStateFlow(Unit)
|
||||||
private var defaultRoomNotificationMode: RoomNotificationMode = initialDefaultMode
|
private var defaultGroupRoomNotificationMode: RoomNotificationMode = initialGroupDefaultMode
|
||||||
private var roomNotificationMode: RoomNotificationMode = initialMode
|
private var defaultEncryptedGroupRoomNotificationMode: RoomNotificationMode = initialEncryptedGroupDefaultMode
|
||||||
|
private var defaultOneToOneRoomNotificationMode: RoomNotificationMode = initialOneToOneDefaultMode
|
||||||
|
private var defaultEncryptedOneToOneRoomNotificationMode: RoomNotificationMode = initialEncryptedOneToOneDefaultMode
|
||||||
|
private var roomNotificationMode: RoomNotificationMode = initialRoomMode
|
||||||
|
private var callNotificationsEnabled = false
|
||||||
|
private var atRoomNotificationsEnabled = false
|
||||||
override val notificationSettingsChangeFlow: SharedFlow<Unit>
|
override val notificationSettingsChangeFlow: SharedFlow<Unit>
|
||||||
get() = _roomNotificationSettingsStateFlow
|
get() = _notificationSettingsStateFlow
|
||||||
|
|
||||||
override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, membersCount: Long): Result<RoomNotificationSettings> {
|
override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationSettings> {
|
||||||
return Result.success(RoomNotificationSettings(mode = roomNotificationMode, isDefault = roomNotificationMode == defaultRoomNotificationMode))
|
return Result.success(
|
||||||
|
RoomNotificationSettings(
|
||||||
|
mode = roomNotificationMode,
|
||||||
|
isDefault = roomNotificationMode == defaultEncryptedGroupRoomNotificationMode
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, membersCount: Long): Result<RoomNotificationMode> {
|
override suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationMode> {
|
||||||
return Result.success(defaultRoomNotificationMode)
|
return if (isOneToOne) {
|
||||||
|
if (isEncrypted) {
|
||||||
|
Result.success(defaultEncryptedOneToOneRoomNotificationMode)
|
||||||
|
} else {
|
||||||
|
Result.success(defaultOneToOneRoomNotificationMode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isEncrypted) {
|
||||||
|
Result.success(defaultEncryptedGroupRoomNotificationMode)
|
||||||
|
} else {
|
||||||
|
Result.success(defaultGroupRoomNotificationMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setDefaultRoomNotificationMode(isEncrypted: Boolean, mode: RoomNotificationMode, isOneToOne: Boolean): Result<Unit> {
|
||||||
|
if (isOneToOne) {
|
||||||
|
if (isEncrypted) {
|
||||||
|
defaultEncryptedOneToOneRoomNotificationMode = mode
|
||||||
|
} else {
|
||||||
|
defaultOneToOneRoomNotificationMode = mode
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isEncrypted) {
|
||||||
|
defaultEncryptedGroupRoomNotificationMode = mode
|
||||||
|
} else {
|
||||||
|
defaultGroupRoomNotificationMode = mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_notificationSettingsStateFlow.emit(Unit)
|
||||||
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result<Unit> {
|
override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result<Unit> {
|
||||||
roomNotificationMode = mode
|
roomNotificationMode = mode
|
||||||
_roomNotificationSettingsStateFlow.emit(Unit)
|
_notificationSettingsStateFlow.emit(Unit)
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result<Unit> {
|
override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result<Unit> {
|
||||||
roomNotificationMode = defaultRoomNotificationMode
|
roomNotificationMode = defaultEncryptedGroupRoomNotificationMode
|
||||||
_roomNotificationSettingsStateFlow.emit(Unit)
|
_notificationSettingsStateFlow.emit(Unit)
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,23 +101,25 @@ class FakeNotificationSettingsService(
|
||||||
return setRoomNotificationMode(roomId, RoomNotificationMode.MUTE)
|
return setRoomNotificationMode(roomId, RoomNotificationMode.MUTE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, membersCount: Long): Result<Unit> {
|
override suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<Unit> {
|
||||||
return restoreDefaultRoomNotificationMode(roomId)
|
return restoreDefaultRoomNotificationMode(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun isRoomMentionEnabled(): Result<Boolean> {
|
override suspend fun isRoomMentionEnabled(): Result<Boolean> {
|
||||||
return Result.success(false)
|
return Result.success(atRoomNotificationsEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setRoomMentionEnabled(enabled: Boolean): Result<Unit> {
|
override suspend fun setRoomMentionEnabled(enabled: Boolean): Result<Unit> {
|
||||||
|
atRoomNotificationsEnabled = enabled
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun isCallEnabled(): Result<Boolean> {
|
override suspend fun isCallEnabled(): Result<Boolean> {
|
||||||
return Result.success(false)
|
return Result.success(callNotificationsEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setCallEnabled(enabled: Boolean): Result<Unit> {
|
override suspend fun setCallEnabled(enabled: Boolean): Result<Unit> {
|
||||||
|
callNotificationsEnabled = enabled
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,6 @@ class FakeMatrixRoom(
|
||||||
private var userAvatarUrlResult = Result.success<String?>(null)
|
private var userAvatarUrlResult = Result.success<String?>(null)
|
||||||
private var updateMembersResult: Result<Unit> = Result.success(Unit)
|
private var updateMembersResult: Result<Unit> = Result.success(Unit)
|
||||||
private var joinRoomResult = Result.success(Unit)
|
private var joinRoomResult = Result.success(Unit)
|
||||||
private var updateRoomNotificationSettingsResult: Result<Unit> = Result.success(Unit)
|
|
||||||
private var inviteUserResult = Result.success(Unit)
|
private var inviteUserResult = Result.success(Unit)
|
||||||
private var canInviteResult = Result.success(true)
|
private var canInviteResult = Result.success(true)
|
||||||
private var canRedactResult = Result.success(canRedact)
|
private var canRedactResult = Result.success(canRedact)
|
||||||
|
|
@ -149,7 +148,7 @@ class FakeMatrixRoom(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateRoomNotificationSettings(): Result<Unit> = simulateLongTask {
|
override suspend fun updateRoomNotificationSettings(): Result<Unit> = simulateLongTask {
|
||||||
val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, activeMemberCount).getOrThrow()
|
val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow()
|
||||||
roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(notificationSettings)
|
roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(notificationSettings)
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
libraries/pushstore/test/build.gradle.kts
Normal file
31
libraries/pushstore/test/build.gradle.kts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("io.element.android-library")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "io.element.android.libraries.pushstore.test"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(projects.libraries.matrix.api)
|
||||||
|
api(libs.coroutines.core)
|
||||||
|
implementation(libs.coroutines.test)
|
||||||
|
implementation(projects.tests.testutils)
|
||||||
|
implementation(projects.libraries.pushstore.api)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.element.android.libraries.pushstore.test.userpushstore
|
||||||
|
|
||||||
|
import io.element.android.libraries.pushstore.api.UserPushStore
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
class FakeUserPushStore: UserPushStore {
|
||||||
|
|
||||||
|
private var pushProviderName: String? = null
|
||||||
|
private var currentRegisteredPushKey: String? = null
|
||||||
|
private val notificationEnabledForDevice = MutableStateFlow(true)
|
||||||
|
override suspend fun getPushProviderName(): String? {
|
||||||
|
return pushProviderName
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setPushProviderName(value: String) {
|
||||||
|
pushProviderName = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCurrentRegisteredPushKey(): String? {
|
||||||
|
return currentRegisteredPushKey
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setCurrentRegisteredPushKey(value: String) {
|
||||||
|
currentRegisteredPushKey = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNotificationEnabledForDevice(): Flow<Boolean> {
|
||||||
|
return notificationEnabledForDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setNotificationEnabledForDevice(enabled: Boolean) {
|
||||||
|
notificationEnabledForDevice.value = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun useCompleteNotificationFormat(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun reset() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.element.android.libraries.pushstore.test.userpushstore
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.pushstore.api.UserPushStore
|
||||||
|
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||||
|
|
||||||
|
class FakeUserPushStoreFactory: UserPushStoreFactory {
|
||||||
|
override fun create(userId: SessionId): UserPushStore {
|
||||||
|
return FakeUserPushStore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue