From 3d0d5ed25031b1ff585151dcf510944f752963ef Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Sun, 21 Apr 2024 19:53:08 +0200 Subject: [PATCH] Login: correctly combine input flows for account details UI state (backport bitfireAT/davx5#575) --- .../davdroid/ui/setup/AccountDetailsPage.kt | 6 +- .../davdroid/ui/setup/LoginScreenModel.kt | 61 +++++++++++-------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsPage.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsPage.kt index 92a9d364c..0c8bdfe80 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsPage.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsPage.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupProperties +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import at.bitfire.davdroid.R import at.bitfire.davdroid.ui.composable.Assistant @@ -52,9 +53,8 @@ fun AccountDetailsPage( onAccountCreated: (Account) -> Unit, model: LoginScreenModel = viewModel() ) { - val uiState = model.accountDetailsUiState - if (uiState.createdAccount != null) - onAccountCreated(uiState.createdAccount) + val uiState by model.accountDetailsUiState.collectAsStateWithLifecycle() + uiState.createdAccount?.let(onAccountCreated) val context = LocalContext.current LaunchedEffect(uiState.couldNotCreateAccount) { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginScreenModel.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginScreenModel.kt index 9030b64d1..506dca0af 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginScreenModel.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginScreenModel.kt @@ -6,7 +6,6 @@ package at.bitfire.davdroid.ui.setup import android.accounts.Account import android.content.Context -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -24,9 +23,12 @@ import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext @@ -246,66 +248,75 @@ class LoginScreenModel @AssistedInject constructor( else null } - .stateIn(viewModelScope, SharingStarted.Eagerly, null) // backing field that is combined with dynamic content for the resulting UI State - private var _accountDetailsUiState by mutableStateOf(AccountDetailsUiState()) - val accountDetailsUiState by derivedStateOf { - val method = forcedGroupMethod.value - + private var _accountDetailsUiState = MutableStateFlow(AccountDetailsUiState()) + val accountDetailsUiState = combine(_accountDetailsUiState, forcedGroupMethod) { uiState, method -> // set group type to read-only if group method is forced - var combinedState = _accountDetailsUiState.copy(groupMethodReadOnly = method != null) + var combinedState = uiState.copy(groupMethodReadOnly = method != null) // apply forced group method, if applicable if (method != null) combinedState = combinedState.copy(groupMethod = method) combinedState - } + }.stateIn(viewModelScope, SharingStarted.Lazily, _accountDetailsUiState.value) fun updateAccountName(accountName: String) { - _accountDetailsUiState = _accountDetailsUiState.copy( - accountName = accountName, - accountNameExists = accountRepository.exists(accountName) - ) + _accountDetailsUiState.update { currentState -> + currentState.copy( + accountName = accountName, + accountNameExists = accountRepository.exists(accountName) + ) + } } fun updateAccountNameAndEmails(accountName: String, emails: Set) { - _accountDetailsUiState = _accountDetailsUiState.copy( - accountName = accountName, - accountNameExists = accountRepository.exists(accountName), - suggestedAccountNames = emails - ) + _accountDetailsUiState.update { currentState -> + currentState.copy( + accountName = accountName, + accountNameExists = accountRepository.exists(accountName), + suggestedAccountNames = emails + ) + } } fun updateGroupMethod(groupMethod: GroupMethod) { - _accountDetailsUiState = _accountDetailsUiState.copy(groupMethod = groupMethod) + _accountDetailsUiState.update { currentState -> + currentState.copy(groupMethod = groupMethod) + } } fun resetCouldNotCreateAccount() { - _accountDetailsUiState = _accountDetailsUiState.copy(couldNotCreateAccount = false) + _accountDetailsUiState.update { currentState -> + currentState.copy(couldNotCreateAccount = false) + } } fun createAccount() { - _accountDetailsUiState = _accountDetailsUiState.copy(creatingAccount = true) + _accountDetailsUiState.update { currentState -> + currentState.copy(creatingAccount = true) + } + viewModelScope.launch { val account = withContext(Dispatchers.Default) { accountRepository.create( - accountDetailsUiState.accountName, + accountDetailsUiState.value.accountName, loginInfo.credentials, foundConfig!!, - accountDetailsUiState.groupMethod + accountDetailsUiState.value.groupMethod ) } - _accountDetailsUiState = + _accountDetailsUiState.update { currentState -> if (account != null) - accountDetailsUiState.copy(createdAccount = account) + currentState.copy(createdAccount = account) else - accountDetailsUiState.copy( + currentState.copy( creatingAccount = false, couldNotCreateAccount = true ) + } } }