Move setting to change push provider to the Notifications setting screen. #2912

Also improve previews of NotificationSettingsView.
This commit is contained in:
Benoit Marty 2024-05-27 11:06:34 +02:00 committed by Benoit Marty
parent 8967b6c959
commit eacc591824
14 changed files with 271 additions and 265 deletions

View file

@ -24,7 +24,4 @@ sealed interface AdvancedSettingsEvents {
data object ChangeTheme : AdvancedSettingsEvents data object ChangeTheme : AdvancedSettingsEvents
data object CancelChangeTheme : AdvancedSettingsEvents data object CancelChangeTheme : AdvancedSettingsEvents
data class SetTheme(val theme: Theme) : AdvancedSettingsEvents data class SetTheme(val theme: Theme) : AdvancedSettingsEvents
data object ChangePushProvider : AdvancedSettingsEvents
data object CancelChangePushProvider : AdvancedSettingsEvents
data class SetPushProvider(val index: Int) : AdvancedSettingsEvents
} }

View file

@ -17,10 +17,8 @@
package io.element.android.features.preferences.impl.advanced package io.element.android.features.preferences.impl.advanced
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -29,22 +27,13 @@ import io.element.android.compound.theme.Theme
import io.element.android.compound.theme.mapToTheme import io.element.android.compound.theme.mapToTheme
import io.element.android.features.preferences.api.store.AppPreferencesStore import io.element.android.features.preferences.api.store.AppPreferencesStore
import io.element.android.features.preferences.api.store.SessionPreferencesStore import io.element.android.features.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.push.api.PushService
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class AdvancedSettingsPresenter @Inject constructor( class AdvancedSettingsPresenter @Inject constructor(
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val sessionPreferencesStore: SessionPreferencesStore, private val sessionPreferencesStore: SessionPreferencesStore,
private val matrixClient: MatrixClient,
private val pushService: PushService,
) : Presenter<AdvancedSettingsState> { ) : Presenter<AdvancedSettingsState> {
@Composable @Composable
override fun present(): AdvancedSettingsState { override fun present(): AdvancedSettingsState {
@ -61,61 +50,6 @@ class AdvancedSettingsPresenter @Inject constructor(
.collectAsState(initial = Theme.System) .collectAsState(initial = Theme.System)
var showChangeThemeDialog by remember { mutableStateOf(false) } var showChangeThemeDialog by remember { mutableStateOf(false) }
// List of PushProvider -> Distributor
val distributors = remember {
pushService.getAvailablePushProviders()
.flatMap { pushProvider ->
pushProvider.getDistributors().map { distributor ->
pushProvider to distributor
}
}
}
// List of Distributor names
val distributorNames = remember {
distributors.map { it.second.name }
}
var currentDistributorName by remember { mutableStateOf<AsyncAction<String>>(AsyncAction.Uninitialized) }
var refreshPushProvider by remember { mutableIntStateOf(0) }
LaunchedEffect(refreshPushProvider) {
val p = pushService.getCurrentPushProvider()
val name = p?.getCurrentDistributor(matrixClient)?.name
currentDistributorName = if (name != null) {
AsyncAction.Success(name)
} else {
AsyncAction.Failure(Exception("Failed to get current push provider"))
}
}
var showChangePushProviderDialog by remember { mutableStateOf(false) }
fun CoroutineScope.changePushProvider(
data: Pair<PushProvider, Distributor>?
) = launch {
showChangePushProviderDialog = false
data ?: return@launch
// No op if the value is the same.
if (data.second.name == currentDistributorName.dataOrNull()) return@launch
currentDistributorName = AsyncAction.Loading
data.let { (pushProvider, distributor) ->
pushService.registerWith(
matrixClient = matrixClient,
pushProvider = pushProvider,
distributor = distributor
)
.fold(
{
currentDistributorName = AsyncAction.Success(distributor.name)
refreshPushProvider++
},
{
currentDistributorName = AsyncAction.Failure(it)
}
)
}
}
fun handleEvents(event: AdvancedSettingsEvents) { fun handleEvents(event: AdvancedSettingsEvents) {
when (event) { when (event) {
is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch {
@ -130,9 +64,6 @@ class AdvancedSettingsPresenter @Inject constructor(
appPreferencesStore.setTheme(event.theme.name) appPreferencesStore.setTheme(event.theme.name)
showChangeThemeDialog = false showChangeThemeDialog = false
} }
AdvancedSettingsEvents.ChangePushProvider -> showChangePushProviderDialog = true
AdvancedSettingsEvents.CancelChangePushProvider -> showChangePushProviderDialog = false
is AdvancedSettingsEvents.SetPushProvider -> localCoroutineScope.changePushProvider(distributors.getOrNull(event.index))
} }
} }
@ -141,9 +72,6 @@ class AdvancedSettingsPresenter @Inject constructor(
isSharePresenceEnabled = isSharePresenceEnabled, isSharePresenceEnabled = isSharePresenceEnabled,
theme = theme, theme = theme,
showChangeThemeDialog = showChangeThemeDialog, showChangeThemeDialog = showChangeThemeDialog,
currentPushDistributor = currentDistributorName,
availablePushDistributors = distributorNames.toImmutableList(),
showChangePushProviderDialog = showChangePushProviderDialog,
eventSink = { handleEvents(it) } eventSink = { handleEvents(it) }
) )
} }

View file

@ -17,16 +17,11 @@
package io.element.android.features.preferences.impl.advanced package io.element.android.features.preferences.impl.advanced
import io.element.android.compound.theme.Theme import io.element.android.compound.theme.Theme
import io.element.android.libraries.architecture.AsyncAction
import kotlinx.collections.immutable.ImmutableList
data class AdvancedSettingsState( data class AdvancedSettingsState(
val isDeveloperModeEnabled: Boolean, val isDeveloperModeEnabled: Boolean,
val isSharePresenceEnabled: Boolean, val isSharePresenceEnabled: Boolean,
val theme: Theme, val theme: Theme,
val showChangeThemeDialog: Boolean, val showChangeThemeDialog: Boolean,
val currentPushDistributor: AsyncAction<String>,
val availablePushDistributors: ImmutableList<String>,
val showChangePushProviderDialog: Boolean,
val eventSink: (AdvancedSettingsEvents) -> Unit val eventSink: (AdvancedSettingsEvents) -> Unit
) )

View file

@ -18,8 +18,6 @@ package io.element.android.features.preferences.impl.advanced
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.compound.theme.Theme import io.element.android.compound.theme.Theme
import io.element.android.libraries.architecture.AsyncAction
import kotlinx.collections.immutable.toImmutableList
open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSettingsState> { open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSettingsState> {
override val values: Sequence<AdvancedSettingsState> override val values: Sequence<AdvancedSettingsState>
@ -28,9 +26,6 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSett
aAdvancedSettingsState(isDeveloperModeEnabled = true), aAdvancedSettingsState(isDeveloperModeEnabled = true),
aAdvancedSettingsState(showChangeThemeDialog = true), aAdvancedSettingsState(showChangeThemeDialog = true),
aAdvancedSettingsState(isSendPublicReadReceiptsEnabled = true), aAdvancedSettingsState(isSendPublicReadReceiptsEnabled = true),
aAdvancedSettingsState(showChangePushProviderDialog = true),
aAdvancedSettingsState(currentPushDistributor = AsyncAction.Loading),
aAdvancedSettingsState(currentPushDistributor = AsyncAction.Failure(Exception("Failed to change distributor"))),
) )
} }
@ -38,17 +33,11 @@ fun aAdvancedSettingsState(
isDeveloperModeEnabled: Boolean = false, isDeveloperModeEnabled: Boolean = false,
isSendPublicReadReceiptsEnabled: Boolean = false, isSendPublicReadReceiptsEnabled: Boolean = false,
showChangeThemeDialog: Boolean = false, showChangeThemeDialog: Boolean = false,
currentPushDistributor: AsyncAction<String> = AsyncAction.Success("Firebase"),
availablePushDistributors: List<String> = listOf("Firebase", "ntfy"),
showChangePushProviderDialog: Boolean = false,
eventSink: (AdvancedSettingsEvents) -> Unit = {}, eventSink: (AdvancedSettingsEvents) -> Unit = {},
) = AdvancedSettingsState( ) = AdvancedSettingsState(
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,
isSharePresenceEnabled = isSendPublicReadReceiptsEnabled, isSharePresenceEnabled = isSendPublicReadReceiptsEnabled,
theme = Theme.System, theme = Theme.System,
showChangeThemeDialog = showChangeThemeDialog, showChangeThemeDialog = showChangeThemeDialog,
currentPushDistributor = currentPushDistributor,
availablePushDistributors = availablePushDistributors.toImmutableList(),
showChangePushProviderDialog = showChangePushProviderDialog,
eventSink = eventSink eventSink = eventSink
) )

View file

@ -16,24 +16,19 @@
package io.element.android.features.preferences.impl.advanced package io.element.android.features.preferences.impl.advanced
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.progressSemantics
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.Theme import io.element.android.compound.theme.Theme
import io.element.android.compound.theme.themes import io.element.android.compound.theme.themes
import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.R
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.components.dialogs.ListOption import io.element.android.libraries.designsystem.components.dialogs.ListOption
import io.element.android.libraries.designsystem.components.dialogs.SingleSelectionDialog import io.element.android.libraries.designsystem.components.dialogs.SingleSelectionDialog
import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.PreferencePage import io.element.android.libraries.designsystem.components.preferences.PreferencePage
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@ -86,34 +81,6 @@ fun AdvancedSettingsView(
), ),
onClick = { state.eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(!state.isSharePresenceEnabled)) } onClick = { state.eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(!state.isSharePresenceEnabled)) }
) )
ListItem(
headlineContent = {
Text(text = stringResource(id = R.string.screen_advanced_settings_push_provider_android))
},
trailingContent = when (state.currentPushDistributor) {
AsyncAction.Uninitialized,
AsyncAction.Confirming,
AsyncAction.Loading -> ListItemContent.Custom {
CircularProgressIndicator(
modifier = Modifier
.progressSemantics()
.size(20.dp),
strokeWidth = 2.dp
)
}
is AsyncAction.Failure -> ListItemContent.Text(
stringResource(id = CommonStrings.common_error)
)
is AsyncAction.Success -> ListItemContent.Text(
state.currentPushDistributor.dataOrNull() ?: ""
)
},
onClick = {
if (state.currentPushDistributor.isReady()) {
state.eventSink(AdvancedSettingsEvents.ChangePushProvider)
}
}
)
} }
if (state.showChangeThemeDialog) { if (state.showChangeThemeDialog) {
@ -130,22 +97,6 @@ fun AdvancedSettingsView(
onDismissRequest = { state.eventSink(AdvancedSettingsEvents.CancelChangeTheme) }, onDismissRequest = { state.eventSink(AdvancedSettingsEvents.CancelChangeTheme) },
) )
} }
if (state.showChangePushProviderDialog) {
SingleSelectionDialog(
title = stringResource(id = R.string.screen_advanced_settings_choose_distributor_dialog_title_android),
options = state.availablePushDistributors.map {
ListOption(title = it)
}.toImmutableList(),
initialSelection = state.availablePushDistributors.indexOf(state.currentPushDistributor.dataOrNull()),
onOptionSelected = { index ->
state.eventSink(
AdvancedSettingsEvents.SetPushProvider(index)
)
},
onDismissRequest = { state.eventSink(AdvancedSettingsEvents.CancelChangePushProvider) },
)
}
} }
@Composable @Composable

View file

@ -25,4 +25,7 @@ sealed interface NotificationSettingsEvents {
data object FixConfigurationMismatch : NotificationSettingsEvents data object FixConfigurationMismatch : NotificationSettingsEvents
data object ClearConfigurationMismatchError : NotificationSettingsEvents data object ClearConfigurationMismatchError : NotificationSettingsEvents
data object ClearNotificationChangeError : NotificationSettingsEvents data object ClearNotificationChangeError : NotificationSettingsEvents
data object ChangePushProvider : NotificationSettingsEvents
data object CancelChangePushProvider : NotificationSettingsEvents
data class SetPushProvider(val index: Int) : NotificationSettingsEvents
} }

View file

@ -20,17 +20,24 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
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 io.element.android.libraries.push.api.PushService
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushstore.api.UserPushStore import io.element.android.libraries.pushstore.api.UserPushStore
import io.element.android.libraries.pushstore.api.UserPushStoreFactory import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
@ -44,7 +51,8 @@ class NotificationSettingsPresenter @Inject constructor(
private val notificationSettingsService: NotificationSettingsService, private val notificationSettingsService: NotificationSettingsService,
private val userPushStoreFactory: UserPushStoreFactory, private val userPushStoreFactory: UserPushStoreFactory,
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val systemNotificationsEnabledProvider: SystemNotificationsEnabledProvider private val pushService: PushService,
private val systemNotificationsEnabledProvider: SystemNotificationsEnabledProvider,
) : Presenter<NotificationSettingsState> { ) : Presenter<NotificationSettingsState> {
@Composable @Composable
override fun present(): NotificationSettingsState { override fun present(): NotificationSettingsState {
@ -68,6 +76,61 @@ class NotificationSettingsPresenter @Inject constructor(
observeNotificationSettings(matrixSettings) observeNotificationSettings(matrixSettings)
} }
// List of PushProvider -> Distributor
val distributors = remember {
pushService.getAvailablePushProviders()
.flatMap { pushProvider ->
pushProvider.getDistributors().map { distributor ->
pushProvider to distributor
}
}
}
// List of Distributor names
val distributorNames = remember {
distributors.map { it.second.name }
}
var currentDistributorName by remember { mutableStateOf<AsyncAction<String>>(AsyncAction.Uninitialized) }
var refreshPushProvider by remember { mutableIntStateOf(0) }
LaunchedEffect(refreshPushProvider) {
val p = pushService.getCurrentPushProvider()
val name = p?.getCurrentDistributor(matrixClient)?.name
currentDistributorName = if (name != null) {
AsyncAction.Success(name)
} else {
AsyncAction.Failure(Exception("Failed to get current push provider"))
}
}
var showChangePushProviderDialog by remember { mutableStateOf(false) }
fun CoroutineScope.changePushProvider(
data: Pair<PushProvider, Distributor>?
) = launch {
showChangePushProviderDialog = false
data ?: return@launch
// No op if the value is the same.
if (data.second.name == currentDistributorName.dataOrNull()) return@launch
currentDistributorName = AsyncAction.Loading
data.let { (pushProvider, distributor) ->
pushService.registerWith(
matrixClient = matrixClient,
pushProvider = pushProvider,
distributor = distributor
)
.fold(
{
currentDistributorName = AsyncAction.Success(distributor.name)
refreshPushProvider++
},
{
currentDistributorName = AsyncAction.Failure(it)
}
)
}
}
fun handleEvents(event: NotificationSettingsEvents) { fun handleEvents(event: NotificationSettingsEvents) {
when (event) { when (event) {
is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> { is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> {
@ -88,6 +151,9 @@ class NotificationSettingsPresenter @Inject constructor(
systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled() systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled()
} }
NotificationSettingsEvents.ClearNotificationChangeError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized NotificationSettingsEvents.ClearNotificationChangeError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized
NotificationSettingsEvents.ChangePushProvider -> showChangePushProviderDialog = true
NotificationSettingsEvents.CancelChangePushProvider -> showChangePushProviderDialog = false
is NotificationSettingsEvents.SetPushProvider -> localCoroutineScope.changePushProvider(distributors.getOrNull(event.index))
} }
} }
@ -98,6 +164,9 @@ class NotificationSettingsPresenter @Inject constructor(
appNotificationsEnabled = appNotificationsEnabled.value appNotificationsEnabled = appNotificationsEnabled.value
), ),
changeNotificationSettingAction = changeNotificationSettingAction.value, changeNotificationSettingAction = changeNotificationSettingAction.value,
currentPushDistributor = currentDistributorName,
availablePushDistributors = distributorNames.toImmutableList(),
showChangePushProviderDialog = showChangePushProviderDialog,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }

View file

@ -19,12 +19,16 @@ package io.element.android.features.preferences.impl.notifications
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import kotlinx.collections.immutable.ImmutableList
@Immutable @Immutable
data class NotificationSettingsState( data class NotificationSettingsState(
val matrixSettings: MatrixSettings, val matrixSettings: MatrixSettings,
val appSettings: AppSettings, val appSettings: AppSettings,
val changeNotificationSettingAction: AsyncAction<Unit>, val changeNotificationSettingAction: AsyncAction<Unit>,
val currentPushDistributor: AsyncAction<String>,
val availablePushDistributors: ImmutableList<String>,
val showChangePushProviderDialog: Boolean,
val eventSink: (NotificationSettingsEvents) -> Unit, val eventSink: (NotificationSettingsEvents) -> Unit,
) { ) {
sealed interface MatrixSettings { sealed interface MatrixSettings {

View file

@ -19,13 +19,19 @@ package io.element.android.features.preferences.impl.notifications
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
open class NotificationSettingsStateProvider : PreviewParameterProvider<NotificationSettingsState> { open class NotificationSettingsStateProvider : PreviewParameterProvider<NotificationSettingsState> {
override val values: Sequence<NotificationSettingsState> override val values: Sequence<NotificationSettingsState>
get() = sequenceOf( get() = sequenceOf(
aValidNotificationSettingsState(), aValidNotificationSettingsState(),
aValidNotificationSettingsState(systemNotificationsEnabled = false),
aValidNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading), aValidNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading),
aValidNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))), aValidNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))),
aValidNotificationSettingsState(showChangePushProviderDialog = true),
aValidNotificationSettingsState(currentPushDistributor = AsyncAction.Loading),
aValidNotificationSettingsState(currentPushDistributor = AsyncAction.Failure(Exception("Failed to change distributor"))),
aInvalidNotificationSettingsState(), aInvalidNotificationSettingsState(),
aInvalidNotificationSettingsState(fixFailed = true), aInvalidNotificationSettingsState(fixFailed = true),
) )
@ -36,7 +42,11 @@ fun aValidNotificationSettingsState(
atRoomNotificationsEnabled: Boolean = true, atRoomNotificationsEnabled: Boolean = true,
callNotificationsEnabled: Boolean = true, callNotificationsEnabled: Boolean = true,
inviteForMeNotificationsEnabled: Boolean = true, inviteForMeNotificationsEnabled: Boolean = true,
systemNotificationsEnabled: Boolean = true,
appNotificationEnabled: Boolean = true, appNotificationEnabled: Boolean = true,
currentPushDistributor: AsyncAction<String> = AsyncAction.Success("Firebase"),
availablePushDistributors: List<String> = listOf("Firebase", "ntfy"),
showChangePushProviderDialog: Boolean = false,
eventSink: (NotificationSettingsEvents) -> Unit = {}, eventSink: (NotificationSettingsEvents) -> Unit = {},
) = NotificationSettingsState( ) = NotificationSettingsState(
matrixSettings = NotificationSettingsState.MatrixSettings.Valid( matrixSettings = NotificationSettingsState.MatrixSettings.Valid(
@ -47,10 +57,13 @@ fun aValidNotificationSettingsState(
defaultOneToOneNotificationMode = RoomNotificationMode.ALL_MESSAGES, defaultOneToOneNotificationMode = RoomNotificationMode.ALL_MESSAGES,
), ),
appSettings = NotificationSettingsState.AppSettings( appSettings = NotificationSettingsState.AppSettings(
systemNotificationsEnabled = false, systemNotificationsEnabled = systemNotificationsEnabled,
appNotificationsEnabled = appNotificationEnabled, appNotificationsEnabled = appNotificationEnabled,
), ),
changeNotificationSettingAction = changeNotificationSettingAction, changeNotificationSettingAction = changeNotificationSettingAction,
currentPushDistributor = currentPushDistributor,
availablePushDistributors = availablePushDistributors.toImmutableList(),
showChangePushProviderDialog = showChangePushProviderDialog,
eventSink = eventSink, eventSink = eventSink,
) )
@ -66,5 +79,8 @@ fun aInvalidNotificationSettingsState(
appNotificationsEnabled = true, appNotificationsEnabled = true,
), ),
changeNotificationSettingAction = AsyncAction.Uninitialized, changeNotificationSettingAction = AsyncAction.Uninitialized,
currentPushDistributor = AsyncAction.Uninitialized,
availablePushDistributors = persistentListOf(),
showChangePushProviderDialog = false,
eventSink = eventSink, eventSink = eventSink,
) )

View file

@ -16,27 +16,38 @@
package io.element.android.features.preferences.impl.notifications package io.element.android.features.preferences.impl.notifications
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.progressSemantics
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.R
import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule
import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.dialogs.ListOption
import io.element.android.libraries.designsystem.components.dialogs.SingleSelectionDialog
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferencePage import io.element.android.libraries.designsystem.components.preferences.PreferencePage
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.toImmutableList
/** /**
* A view that allows a user edit their global notification settings. * A view that allows a user edit their global notification settings.
@ -69,7 +80,7 @@ fun NotificationSettingsView(
NotificationSettingsState.MatrixSettings.Uninitialized -> return@PreferencePage NotificationSettingsState.MatrixSettings.Uninitialized -> return@PreferencePage
is NotificationSettingsState.MatrixSettings.Valid -> NotificationSettingsContentView( is NotificationSettingsState.MatrixSettings.Valid -> NotificationSettingsContentView(
matrixSettings = state.matrixSettings, matrixSettings = state.matrixSettings,
systemSettings = state.appSettings, state = state,
onNotificationsEnabledChanged = { state.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(it)) }, onNotificationsEnabledChanged = { state.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(it)) },
onGroupChatsClicked = { onOpenEditDefault(false) }, onGroupChatsClicked = { onOpenEditDefault(false) },
onDirectChatsClicked = { onOpenEditDefault(true) }, onDirectChatsClicked = { onOpenEditDefault(true) },
@ -92,7 +103,7 @@ fun NotificationSettingsView(
@Composable @Composable
private fun NotificationSettingsContentView( private fun NotificationSettingsContentView(
matrixSettings: NotificationSettingsState.MatrixSettings.Valid, matrixSettings: NotificationSettingsState.MatrixSettings.Valid,
systemSettings: NotificationSettingsState.AppSettings, state: NotificationSettingsState,
onNotificationsEnabledChanged: (Boolean) -> Unit, onNotificationsEnabledChanged: (Boolean) -> Unit,
onGroupChatsClicked: () -> Unit, onGroupChatsClicked: () -> Unit,
onDirectChatsClicked: () -> Unit, onDirectChatsClicked: () -> Unit,
@ -103,6 +114,7 @@ private fun NotificationSettingsContentView(
onTroubleshootNotificationsClicked: () -> Unit, onTroubleshootNotificationsClicked: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val systemSettings: NotificationSettingsState.AppSettings = state.appSettings
if (systemSettings.appNotificationsEnabled && !systemSettings.systemNotificationsEnabled) { if (systemSettings.appNotificationsEnabled && !systemSettings.systemNotificationsEnabled) {
PreferenceText( PreferenceText(
icon = CompoundIcons.NotificationsOffSolid(), icon = CompoundIcons.NotificationsOffSolid(),
@ -169,6 +181,51 @@ private fun NotificationSettingsContentView(
onClick = onTroubleshootNotificationsClicked onClick = onTroubleshootNotificationsClicked
) )
} }
PreferenceCategory(title = stringResource(id = CommonStrings.common_advanced_settings)) {
ListItem(
headlineContent = {
Text(text = stringResource(id = R.string.screen_advanced_settings_push_provider_android))
},
trailingContent = when (state.currentPushDistributor) {
AsyncAction.Uninitialized,
AsyncAction.Confirming,
AsyncAction.Loading -> ListItemContent.Custom {
CircularProgressIndicator(
modifier = Modifier
.progressSemantics()
.size(20.dp),
strokeWidth = 2.dp
)
}
is AsyncAction.Failure -> ListItemContent.Text(
stringResource(id = CommonStrings.common_error)
)
is AsyncAction.Success -> ListItemContent.Text(
state.currentPushDistributor.dataOrNull() ?: ""
)
},
onClick = {
if (state.currentPushDistributor.isReady()) {
state.eventSink(NotificationSettingsEvents.ChangePushProvider)
}
}
)
}
if (state.showChangePushProviderDialog) {
SingleSelectionDialog(
title = stringResource(id = R.string.screen_advanced_settings_choose_distributor_dialog_title_android),
options = state.availablePushDistributors.map {
ListOption(title = it)
}.toImmutableList(),
initialSelection = state.availablePushDistributors.indexOf(state.currentPushDistributor.dataOrNull()),
onOptionSelected = { index ->
state.eventSink(
NotificationSettingsEvents.SetPushProvider(index)
)
},
onDismissRequest = { state.eventSink(NotificationSettingsEvents.CancelChangePushProvider) },
)
}
} }
} }

View file

@ -21,16 +21,8 @@ import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.compound.theme.Theme import io.element.android.compound.theme.Theme
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
import io.element.android.libraries.push.api.PushService
import io.element.android.libraries.push.test.FakePushService
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushproviders.test.FakePushProvider
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.awaitLastSequentialItem
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -108,93 +100,11 @@ class AdvancedSettingsPresenterTest {
} }
} }
@Test
fun `present - change push provider`() = runTest {
val presenter = createAdvancedSettingsPresenter(
pushService = createFakePushService(),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitLastSequentialItem()
assertThat(initialState.currentPushDistributor).isEqualTo(AsyncAction.Success("aDistributorName0"))
assertThat(initialState.availablePushDistributors).containsExactly("aDistributorName0", "aDistributorName1")
initialState.eventSink.invoke(AdvancedSettingsEvents.ChangePushProvider)
val withDialog = awaitItem()
assertThat(withDialog.showChangePushProviderDialog).isTrue()
// Cancel
withDialog.eventSink(AdvancedSettingsEvents.CancelChangePushProvider)
val withoutDialog = awaitItem()
assertThat(withoutDialog.showChangePushProviderDialog).isFalse()
withDialog.eventSink.invoke(AdvancedSettingsEvents.ChangePushProvider)
assertThat(awaitItem().showChangePushProviderDialog).isTrue()
withDialog.eventSink(AdvancedSettingsEvents.SetPushProvider(1))
val withNewProvider = awaitItem()
assertThat(withNewProvider.showChangePushProviderDialog).isFalse()
assertThat(withNewProvider.currentPushDistributor).isEqualTo(AsyncAction.Loading)
val lastItem = awaitItem()
assertThat(lastItem.currentPushDistributor).isEqualTo(AsyncAction.Success("aDistributorName1"))
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - change push provider error`() = runTest {
val presenter = createAdvancedSettingsPresenter(
pushService = createFakePushService(
registerWithLambda = { _, _, _ ->
Result.failure(Exception("An error"))
},
),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitLastSequentialItem()
initialState.eventSink.invoke(AdvancedSettingsEvents.ChangePushProvider)
val withDialog = awaitItem()
assertThat(withDialog.showChangePushProviderDialog).isTrue()
withDialog.eventSink(AdvancedSettingsEvents.SetPushProvider(1))
val withNewProvider = awaitItem()
assertThat(withNewProvider.showChangePushProviderDialog).isFalse()
assertThat(withNewProvider.currentPushDistributor).isEqualTo(AsyncAction.Loading)
val lastItem = awaitItem()
assertThat(lastItem.currentPushDistributor).isInstanceOf(AsyncAction.Failure::class.java)
}
}
private fun createFakePushService(
registerWithLambda: suspend (MatrixClient, PushProvider, Distributor) -> Result<Unit> = { _, _, _ ->
Result.success(Unit)
}
): PushService {
val pushProvider1 = FakePushProvider(
index = 0,
name = "aFakePushProvider0",
isAvailable = true,
distributors = listOf(Distributor("aDistributorValue0", "aDistributorName0")),
)
val pushProvider2 = FakePushProvider(
index = 1,
name = "aFakePushProvider1",
isAvailable = true,
distributors = listOf(Distributor("aDistributorValue1", "aDistributorName1")),
)
return FakePushService(
availablePushProviders = listOf(pushProvider1, pushProvider2),
registerWithLambda = registerWithLambda,
)
}
private fun createAdvancedSettingsPresenter( private fun createAdvancedSettingsPresenter(
appPreferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(), appPreferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(), sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
matrixClient: MatrixClient = FakeMatrixClient(),
pushService: PushService = FakePushService(),
) = AdvancedSettingsPresenter( ) = AdvancedSettingsPresenter(
appPreferencesStore = appPreferencesStore, appPreferencesStore = appPreferencesStore,
sessionPreferencesStore = sessionPreferencesStore, sessionPreferencesStore = sessionPreferencesStore,
matrixClient = matrixClient,
pushService = pushService,
) )
} }

View file

@ -19,8 +19,6 @@ package io.element.android.features.preferences.impl.advanced
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.compound.theme.Theme import io.element.android.compound.theme.Theme
import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.R
@ -34,7 +32,6 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TestRule import org.junit.rules.TestRule
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class AdvancedSettingsViewTest { class AdvancedSettingsViewTest {
@ -103,33 +100,6 @@ class AdvancedSettingsViewTest {
rule.clickOn(R.string.screen_advanced_settings_share_presence) rule.clickOn(R.string.screen_advanced_settings_share_presence)
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetSharePresenceEnabled(true)) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetSharePresenceEnabled(true))
} }
@Config(qualifiers = "h1024dp")
@Test
fun `clicking on Push notification provider emits the expected event`() {
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
rule.setAdvancedSettingsView(
state = aAdvancedSettingsState(
eventSink = eventsRecorder
),
)
rule.clickOn(R.string.screen_advanced_settings_push_provider_android)
eventsRecorder.assertSingle(AdvancedSettingsEvents.ChangePushProvider)
}
@Test
fun `clicking on a push provider emits the expected event`() {
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
rule.setAdvancedSettingsView(
state = aAdvancedSettingsState(
eventSink = eventsRecorder,
showChangePushProviderDialog = true,
availablePushDistributors = listOf("P1", "P2")
),
)
rule.onNodeWithText("P2").performClick()
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetPushProvider(1))
}
} }
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAdvancedSettingsView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAdvancedSettingsView(

View file

@ -20,11 +20,19 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.MatrixClient
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_THROWABLE import io.element.android.libraries.matrix.test.A_THROWABLE
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.push.api.PushService
import io.element.android.libraries.push.test.FakePushService
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushproviders.test.FakePushProvider
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
import io.element.android.tests.testutils.awaitLastSequentialItem
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
@ -230,14 +238,94 @@ class NotificationSettingsPresenterTests {
} }
} }
@Test
fun `present - change push provider`() = runTest {
val presenter = createNotificationSettingsPresenter(
pushService = createFakePushService(),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitLastSequentialItem()
assertThat(initialState.currentPushDistributor).isEqualTo(AsyncAction.Success("aDistributorName0"))
assertThat(initialState.availablePushDistributors).containsExactly("aDistributorName0", "aDistributorName1")
initialState.eventSink.invoke(NotificationSettingsEvents.ChangePushProvider)
val withDialog = awaitItem()
assertThat(withDialog.showChangePushProviderDialog).isTrue()
// Cancel
withDialog.eventSink(NotificationSettingsEvents.CancelChangePushProvider)
val withoutDialog = awaitItem()
assertThat(withoutDialog.showChangePushProviderDialog).isFalse()
withDialog.eventSink.invoke(NotificationSettingsEvents.ChangePushProvider)
assertThat(awaitItem().showChangePushProviderDialog).isTrue()
withDialog.eventSink(NotificationSettingsEvents.SetPushProvider(1))
val withNewProvider = awaitItem()
assertThat(withNewProvider.showChangePushProviderDialog).isFalse()
assertThat(withNewProvider.currentPushDistributor).isEqualTo(AsyncAction.Loading)
val lastItem = awaitItem()
assertThat(lastItem.currentPushDistributor).isEqualTo(AsyncAction.Success("aDistributorName1"))
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - change push provider error`() = runTest {
val presenter = createNotificationSettingsPresenter(
pushService = createFakePushService(
registerWithLambda = { _, _, _ ->
Result.failure(Exception("An error"))
},
),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitLastSequentialItem()
initialState.eventSink.invoke(NotificationSettingsEvents.ChangePushProvider)
val withDialog = awaitItem()
assertThat(withDialog.showChangePushProviderDialog).isTrue()
withDialog.eventSink(NotificationSettingsEvents.SetPushProvider(1))
val withNewProvider = awaitItem()
assertThat(withNewProvider.showChangePushProviderDialog).isFalse()
assertThat(withNewProvider.currentPushDistributor).isEqualTo(AsyncAction.Loading)
val lastItem = awaitItem()
assertThat(lastItem.currentPushDistributor).isInstanceOf(AsyncAction.Failure::class.java)
}
}
private fun createFakePushService(
registerWithLambda: suspend (MatrixClient, PushProvider, Distributor) -> Result<Unit> = { _, _, _ ->
Result.success(Unit)
}
): PushService {
val pushProvider1 = FakePushProvider(
index = 0,
name = "aFakePushProvider0",
isAvailable = true,
distributors = listOf(Distributor("aDistributorValue0", "aDistributorName0")),
)
val pushProvider2 = FakePushProvider(
index = 1,
name = "aFakePushProvider1",
isAvailable = true,
distributors = listOf(Distributor("aDistributorValue1", "aDistributorName1")),
)
return FakePushService(
availablePushProviders = listOf(pushProvider1, pushProvider2),
registerWithLambda = registerWithLambda,
)
}
private fun createNotificationSettingsPresenter( private fun createNotificationSettingsPresenter(
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
pushService: PushService = FakePushService(),
): NotificationSettingsPresenter { ): NotificationSettingsPresenter {
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
return NotificationSettingsPresenter( return NotificationSettingsPresenter(
notificationSettingsService = notificationSettingsService, notificationSettingsService = notificationSettingsService,
userPushStoreFactory = FakeUserPushStoreFactory(), userPushStoreFactory = FakeUserPushStoreFactory(),
matrixClient = matrixClient, matrixClient = matrixClient,
pushService = pushService,
systemNotificationsEnabledProvider = FakeSystemNotificationsEnabledProvider(), systemNotificationsEnabledProvider = FakeSystemNotificationsEnabledProvider(),
) )
} }

View file

@ -19,6 +19,8 @@ package io.element.android.features.preferences.impl.notifications
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.R
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
@ -248,6 +250,33 @@ class NotificationSettingsViewTest {
) )
) )
} }
@Config(qualifiers = "h1024dp")
@Test
fun `clicking on Push notification provider emits the expected event`() {
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
rule.setNotificationSettingsView(
state = aValidNotificationSettingsState(
eventSink = eventsRecorder
),
)
rule.clickOn(R.string.screen_advanced_settings_push_provider_android)
eventsRecorder.assertSingle(NotificationSettingsEvents.ChangePushProvider)
}
@Test
fun `clicking on a push provider emits the expected event`() {
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
rule.setNotificationSettingsView(
state = aValidNotificationSettingsState(
eventSink = eventsRecorder,
showChangePushProviderDialog = true,
availablePushDistributors = listOf("P1", "P2")
),
)
rule.onNodeWithText("P2").performClick()
eventsRecorder.assertSingle(NotificationSettingsEvents.SetPushProvider(1))
}
} }
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setNotificationSettingsView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setNotificationSettingsView(