Improve code and display error.

This commit is contained in:
Benoit Marty 2024-05-07 09:47:46 +02:00 committed by Benoit Marty
parent 4bd01b6f4f
commit e64051f7bd
6 changed files with 94 additions and 38 deletions

View file

@ -26,5 +26,5 @@ sealed interface AdvancedSettingsEvents {
data class SetTheme(val theme: Theme) : AdvancedSettingsEvents data class SetTheme(val theme: Theme) : AdvancedSettingsEvents
data object ChangePushProvider : AdvancedSettingsEvents data object ChangePushProvider : AdvancedSettingsEvents
data object CancelChangePushProvider : AdvancedSettingsEvents data object CancelChangePushProvider : AdvancedSettingsEvents
data class SetPushProvider(val distributorName: String) : AdvancedSettingsEvents data class SetPushProvider(val index: Int) : AdvancedSettingsEvents
} }

View file

@ -29,10 +29,14 @@ 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.matrix.api.MatrixClient
import io.element.android.libraries.push.api.PushService 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.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -57,20 +61,60 @@ class AdvancedSettingsPresenter @Inject constructor(
.collectAsState(initial = Theme.System) .collectAsState(initial = Theme.System)
var showChangeThemeDialog by remember { mutableStateOf(false) } var showChangeThemeDialog by remember { mutableStateOf(false) }
var currentPushProvider by remember { mutableStateOf<String?>(null) } // List of PushProvider -> Distributor
var distributors by remember { mutableStateOf<List<String>>(emptyList()) } var distributors by remember { mutableStateOf<List<Pair<PushProvider, Distributor>>>(emptyList()) }
var distributorNames by remember { mutableStateOf<List<String>>(emptyList()) }
LaunchedEffect(Unit) {
distributors = pushService.getAvailablePushProviders()
.flatMap { pushProvider ->
pushProvider.getDistributors().map { distributor ->
pushProvider to distributor
}
}
distributorNames = distributors.map { it.second.name }
}
var currentDistributorName by remember { mutableStateOf<AsyncAction<String>>(AsyncAction.Uninitialized) }
var refreshPushProvider by remember { mutableIntStateOf(0) } var refreshPushProvider by remember { mutableIntStateOf(0) }
LaunchedEffect(refreshPushProvider) { LaunchedEffect(refreshPushProvider) {
val p = pushService.getCurrentPushProvider() val p = pushService.getCurrentPushProvider()
currentPushProvider = p?.getCurrentDistributor(matrixClient)?.name val name = p?.getCurrentDistributor(matrixClient)?.name
distributors = pushService.getAvailablePushProviders() currentDistributorName = if (name != null) {
.flatMap { pushProvider -> AsyncAction.Success(name)
pushProvider.getDistributors().map { it.name } } else {
} AsyncAction.Failure(Exception("Failed to get current push provider"))
}
} }
var showChangePushProviderDialog by remember { mutableStateOf(false) } 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 {
@ -87,23 +131,7 @@ class AdvancedSettingsPresenter @Inject constructor(
} }
AdvancedSettingsEvents.ChangePushProvider -> showChangePushProviderDialog = true AdvancedSettingsEvents.ChangePushProvider -> showChangePushProviderDialog = true
AdvancedSettingsEvents.CancelChangePushProvider -> showChangePushProviderDialog = false AdvancedSettingsEvents.CancelChangePushProvider -> showChangePushProviderDialog = false
is AdvancedSettingsEvents.SetPushProvider -> { is AdvancedSettingsEvents.SetPushProvider -> localCoroutineScope.changePushProvider(distributors.getOrNull(event.index))
localCoroutineScope.launch {
// Retrieve the push provider
// TODO rework this
val pushProvider = pushService.getAvailablePushProviders().firstOrNull { pushProvider ->
pushProvider.getDistributors().any { it.name == event.distributorName }
} ?: return@launch
val distributor = pushProvider.getDistributors().firstOrNull { it.name == event.distributorName } ?: return@launch
pushService.registerWith(
matrixClient,
pushProvider = pushProvider,
distributor = distributor
)
showChangePushProviderDialog = false
refreshPushProvider++
}
}
} }
} }
@ -112,10 +140,11 @@ class AdvancedSettingsPresenter @Inject constructor(
isSharePresenceEnabled = isSharePresenceEnabled, isSharePresenceEnabled = isSharePresenceEnabled,
theme = theme, theme = theme,
showChangeThemeDialog = showChangeThemeDialog, showChangeThemeDialog = showChangeThemeDialog,
pushDistributor = currentPushProvider ?: "", pushDistributor = currentDistributorName,
pushDistributors = distributors.toImmutableList(), pushDistributors = distributorNames.toImmutableList(),
showChangePushProviderDialog = showChangePushProviderDialog, showChangePushProviderDialog = showChangePushProviderDialog,
eventSink = { handleEvents(it) } eventSink = { handleEvents(it) }
) )
} }
} }

View file

@ -17,6 +17,7 @@
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 import kotlinx.collections.immutable.ImmutableList
data class AdvancedSettingsState( data class AdvancedSettingsState(
@ -24,7 +25,7 @@ data class AdvancedSettingsState(
val isSharePresenceEnabled: Boolean, val isSharePresenceEnabled: Boolean,
val theme: Theme, val theme: Theme,
val showChangeThemeDialog: Boolean, val showChangeThemeDialog: Boolean,
val pushDistributor: String, val pushDistributor: AsyncAction<String>,
val pushDistributors: ImmutableList<String>, val pushDistributors: ImmutableList<String>,
val showChangePushProviderDialog: Boolean, val showChangePushProviderDialog: Boolean,
val eventSink: (AdvancedSettingsEvents) -> Unit val eventSink: (AdvancedSettingsEvents) -> Unit

View file

@ -18,6 +18,7 @@ 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 import kotlinx.collections.immutable.toImmutableList
open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSettingsState> { open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSettingsState> {
@ -28,6 +29,8 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSett
aAdvancedSettingsState(showChangeThemeDialog = true), aAdvancedSettingsState(showChangeThemeDialog = true),
aAdvancedSettingsState(isSendPublicReadReceiptsEnabled = true), aAdvancedSettingsState(isSendPublicReadReceiptsEnabled = true),
aAdvancedSettingsState(showChangePushProviderDialog = true), aAdvancedSettingsState(showChangePushProviderDialog = true),
aAdvancedSettingsState(pushDistributor = AsyncAction.Loading),
aAdvancedSettingsState(pushDistributor = AsyncAction.Failure(Exception("Failed to change distributor"))),
) )
} }
@ -35,7 +38,7 @@ fun aAdvancedSettingsState(
isDeveloperModeEnabled: Boolean = false, isDeveloperModeEnabled: Boolean = false,
isSendPublicReadReceiptsEnabled: Boolean = false, isSendPublicReadReceiptsEnabled: Boolean = false,
showChangeThemeDialog: Boolean = false, showChangeThemeDialog: Boolean = false,
pushDistributor: String = "Firebase", pushDistributor: AsyncAction<String> = AsyncAction.Success("Firebase"),
pushDistributors: List<String> = listOf("Firebase", "ntfy"), pushDistributors: List<String> = listOf("Firebase", "ntfy"),
showChangePushProviderDialog: Boolean = false, showChangePushProviderDialog: Boolean = false,
) = AdvancedSettingsState( ) = AdvancedSettingsState(

View file

@ -16,19 +16,24 @@
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,11 +91,28 @@ fun AdvancedSettingsView(
// TODO i18n // TODO i18n
Text(text = "Push provider") Text(text = "Push provider")
}, },
trailingContent = ListItemContent.Text( trailingContent = when (state.pushDistributor) {
state.pushDistributor 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.pushDistributor.dataOrNull() ?: ""
)
},
onClick = { onClick = {
state.eventSink(AdvancedSettingsEvents.ChangePushProvider) if (state.pushDistributor.isReady()) {
state.eventSink(AdvancedSettingsEvents.ChangePushProvider)
}
} }
) )
} }
@ -112,15 +134,14 @@ fun AdvancedSettingsView(
if (state.showChangePushProviderDialog) { if (state.showChangePushProviderDialog) {
SingleSelectionDialog( SingleSelectionDialog(
title = "Select Push provider",
options = state.pushDistributors.map { options = state.pushDistributors.map {
ListOption(title = it) ListOption(title = it)
}.toImmutableList(), }.toImmutableList(),
initialSelection = state.pushDistributors.indexOf(state.pushDistributor), initialSelection = state.pushDistributors.indexOf(state.pushDistributor.dataOrNull()),
onOptionSelected = { onOptionSelected = { index ->
state.eventSink( state.eventSink(
AdvancedSettingsEvents.SetPushProvider( AdvancedSettingsEvents.SetPushProvider(index)
state.pushDistributors[it]
)
) )
}, },
onDismissRequest = { state.eventSink(AdvancedSettingsEvents.CancelChangePushProvider) }, onDismissRequest = { state.eventSink(AdvancedSettingsEvents.CancelChangePushProvider) },

View file

@ -86,6 +86,8 @@ sealed interface AsyncAction<out T> {
fun isFailure(): Boolean = this is Failure fun isFailure(): Boolean = this is Failure
fun isSuccess(): Boolean = this is Success fun isSuccess(): Boolean = this is Success
fun isReady() = isSuccess() || isFailure()
} }
suspend inline fun <T> MutableState<AsyncAction<T>>.runCatchingUpdatingState( suspend inline fun <T> MutableState<AsyncAction<T>>.runCatchingUpdatingState(