Skip to content

Commit

Permalink
Create account: user feedback when account name is already taken (#701)
Browse files Browse the repository at this point in the history
* Show snackbar when account can't be created without known reason; show error when account name is taken

* Don't crash on empty account name
  • Loading branch information
rfc2822 committed Apr 4, 2024
1 parent 7b2f14d commit e638f5d
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.RadioButton
import androidx.compose.material.SnackbarHostState
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Email
Expand All @@ -29,9 +30,11 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
Expand All @@ -43,9 +46,11 @@ import at.bitfire.davdroid.servicedetection.DavResourceFinder
import at.bitfire.davdroid.ui.composable.Assistant
import at.bitfire.davdroid.ui.widget.ExceptionInfoDialog
import at.bitfire.vcard4android.GroupMethod
import kotlinx.coroutines.launch

@Composable
fun AccountDetailsPage(
snackbarHostState: SnackbarHostState,
loginInfo: LoginInfo,
foundConfig: DavResourceFinder.Configuration,
onBack: () -> Unit,
Expand All @@ -54,6 +59,9 @@ fun AccountDetailsPage(
) {
BackHandler(onBack = onBack)

val context = LocalContext.current
val scope = rememberCoroutineScope()

val resultOrNull by model.createAccountResult.observeAsState()
var showExceptionInfo by remember { mutableStateOf(false) }
LaunchedEffect(resultOrNull) {
Expand All @@ -73,9 +81,16 @@ fun AccountDetailsPage(
model.createAccountResult.value = null
}
)
// TODO else
}
else
scope.launch {
snackbarHostState.showSnackbar(context.getString(R.string.login_account_not_created))
}
}
else -> {}
}

// reset result
model.createAccountResult.value = null
}

val suggestedAccountNames = foundConfig.calDAV?.emails ?: emptyList()
Expand All @@ -86,6 +101,7 @@ fun AccountDetailsPage(
AccountDetailsPage_Content(
suggestedAccountNames = suggestedAccountNames,
accountName = accountName,
accountNameAlreadyExists = model.accountExists(accountName).observeAsState(false).value,
onUpdateAccountName = { accountName = it },
onCreateAccount = {
model.createAccount(
Expand All @@ -106,6 +122,7 @@ fun AccountDetailsPage(
fun AccountDetailsPage_Content(
suggestedAccountNames: List<String>,
accountName: String,
accountNameAlreadyExists: Boolean,
onUpdateAccountName: (String) -> Unit = {},
groupMethod: GroupMethod,
groupMethodReadOnly: Boolean,
Expand All @@ -114,18 +131,24 @@ fun AccountDetailsPage_Content(
) {
Assistant(
nextLabel = stringResource(R.string.login_create_account),
onNext = onCreateAccount
onNext = onCreateAccount,
nextEnabled = accountName.isNotBlank() && !accountNameAlreadyExists
) {
Column(Modifier.padding(8.dp)) {
var expanded by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it }
) {
val accountNameLabel = if (accountNameAlreadyExists)
stringResource(R.string.login_account_name_already_taken)
else
stringResource(R.string.login_account_name)
OutlinedTextField(
value = accountName,
onValueChange = onUpdateAccountName,
label = { Text(stringResource(R.string.login_account_name)) },
label = { Text(accountNameLabel) },
isError = accountNameAlreadyExists,
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email
Expand Down Expand Up @@ -227,6 +250,7 @@ fun AccountDetailsPage_Content_Preview() {
AccountDetailsPage_Content(
suggestedAccountNames = listOf("name1", "name2@example.com"),
accountName = "account@example.com",
accountNameAlreadyExists = false,
groupMethod = GroupMethod.GROUP_VCARDS,
groupMethodReadOnly = false
)
Expand All @@ -238,6 +262,7 @@ fun AccountDetailsPage_Content_Preview_With_Apostrophe() {
AccountDetailsPage_Content(
suggestedAccountNames = listOf("name1", "name2@example.com"),
accountName = "account'example.com",
accountNameAlreadyExists = true,
groupMethod = GroupMethod.CATEGORIES,
groupMethodReadOnly = true
)
Expand Down
16 changes: 16 additions & 0 deletions app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
package at.bitfire.davdroid.ui.setup

import android.accounts.Account
import android.accounts.AccountManager
import android.app.Application
import android.content.ContentResolver
import android.provider.CalendarContract
import androidx.core.os.CancellationSignal
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope
import at.bitfire.davdroid.InvalidAccountException
Expand Down Expand Up @@ -77,6 +80,19 @@ class LoginModel @Inject constructor(
}


fun accountExists(accountName: String): LiveData<Boolean> = liveData {
val accountType = context.getString(R.string.account_type)
val exists =
if (accountName.isEmpty())
false
else
AccountManager.get(context)
.getAccountsByType(accountType)
.contains(Account(accountName, accountType))
emit(exists)
}


interface CreateAccountResult {
class Success(val account: Account): CreateAccountResult
class Error(val exception: Exception?): CreateAccountResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ fun LoginScreen(
foundConfig?.let {
val context = LocalContext.current
AccountDetailsPage(
snackbarHostState = snackbarHostState,
loginInfo = loginInfo,
foundConfig = it,
onBack = { phase = LoginActivity.Phase.LOGIN_TYPE },
Expand Down

0 comments on commit e638f5d

Please sign in to comment.