Autofocus race condition with UI

This commit is contained in:
2026-09-09 09:20:47 +02:00
parent d7a51a797f
commit d2d713c616
@@ -63,10 +63,16 @@ fun EditBillScreen(
val context = LocalContext.current val context = LocalContext.current
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { // The activity reads the project off the main thread and only then reports whether this is a
if (viewModel.isNewBill) { // new bill, so the flag arrives after the first composition. Keying on it rather than on Unit
focusRequester.requestFocus() // is what makes the effect run at all.
} LaunchedEffect(viewModel.isNewBill) {
if (!viewModel.isNewBill) return@LaunchedEffect
// The field has to be laid out before it can take focus, which is not yet true on the
// pass that composed it. Waiting for the next frame makes this hold whether the flag was
// already set or arrived later.
withFrameNanos { }
focusRequester.requestFocus()
} }
StatefulAlertDialog( StatefulAlertDialog(
@@ -198,7 +204,7 @@ fun BillBasicInfoSection(
OutlinedTextField( OutlinedTextField(
value = viewModel.amount, value = viewModel.amount,
onValueChange = { nv-> onValueChange = { nv ->
val filteredValue = nv.filter { it in "0123456789.+-*/" } val filteredValue = nv.filter { it in "0123456789.+-*/" }
viewModel.amount = filteredValue viewModel.amount = filteredValue
viewModel.updateSplits() viewModel.updateSplits()
@@ -207,8 +213,8 @@ fun BillBasicInfoSection(
placeholder = { Text("0") }, placeholder = { Text("0") },
modifier = Modifier.fillMaxWidth().focusRequester(amountFocusRequester), modifier = Modifier.fillMaxWidth().focusRequester(amountFocusRequester),
leadingIcon = { leadingIcon = {
val currencyToShow = viewModel.selectedCurrencyName.ifEmpty { val currencyToShow = viewModel.selectedCurrencyName.ifEmpty {
viewModel.mainCurrencyName.ifEmpty { "$" } viewModel.mainCurrencyName.ifEmpty { "$" }
} }
TextIconDisplay( TextIconDisplay(
textIcon = TextIcon.Symbol(currencyToShow), textIcon = TextIcon.Symbol(currencyToShow),
@@ -220,8 +226,8 @@ fun BillBasicInfoSection(
enabled = canEdit, enabled = canEdit,
onClick = { onClick = {
val mainLabel = viewModel.mainCurrencyName.ifEmpty { "$" } val mainLabel = viewModel.mainCurrencyName.ifEmpty { "$" }
val options = listOf("$mainLabel | Base") + viewModel.currencies.map { val options = listOf("$mainLabel | Base") + viewModel.currencies.map {
"${it.name} | 1 $mainLabel = ${it.exchangeRate} ${it.name}" "${it.name} | 1 $mainLabel = ${it.exchangeRate} ${it.name}"
} }
viewModel.showDialog( viewModel.showDialog(
title = currencyDialogTitle, title = currencyDialogTitle,
@@ -252,8 +258,8 @@ fun BillBasicInfoSection(
placeholder = { Text(stringResource(R.string.label_what)) }, placeholder = { Text(stringResource(R.string.label_what)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) }, leadingIcon = { Icon(Icons.Default.Title, contentDescription = null) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Next) }) keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -463,7 +469,7 @@ fun OwerSelectionSection(
} else { } else {
viewModel.owersCustomSplit[member.id] ?: "" viewModel.owersCustomSplit[member.id] ?: ""
} }
BasicTextField( BasicTextField(
value = value, value = value,
onValueChange = { nv -> onValueChange = { nv ->
@@ -528,7 +534,10 @@ fun BillAdditionalDetailsSection(
val context = LocalContext.current val context = LocalContext.current
var categoryExpanded by remember { mutableStateOf(false) } var categoryExpanded by remember { mutableStateOf(false) }
val selectedCategory = val selectedCategory =
categories.find { it.id == viewModel.categoryId } ?: CategoryUtils.getCategoryById(context, viewModel.categoryId) categories.find { it.id == viewModel.categoryId } ?: CategoryUtils.getCategoryById(
context,
viewModel.categoryId
)
EditableExposedDropdownMenu( EditableExposedDropdownMenu(
value = selectedCategory?.name ?: "", value = selectedCategory?.name ?: "",
@@ -555,7 +564,7 @@ fun BillAdditionalDetailsSection(
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Text(stringResource(R.string.category_none)) Text(stringResource(R.string.category_none))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
viewModel.categoryId = DBBill.CATEGORY_REIMBURSEMENT viewModel.categoryId = DBBill.CATEGORY_REIMBURSEMENT
categoryExpanded = false categoryExpanded = false
@@ -582,7 +591,10 @@ fun BillAdditionalDetailsSection(
var pmExpanded by remember { mutableStateOf(false) } var pmExpanded by remember { mutableStateOf(false) }
val selectedPm = val selectedPm =
paymentModes.find { it.id == viewModel.paymentModeId } ?: CategoryUtils.getPaymentModeById(context, viewModel.paymentModeId) paymentModes.find { it.id == viewModel.paymentModeId } ?: CategoryUtils.getPaymentModeById(
context,
viewModel.paymentModeId
)
EditableExposedDropdownMenu( EditableExposedDropdownMenu(
value = selectedPm?.name ?: "", value = selectedPm?.name ?: "",