android: checklist rows were still 48dp of touch target
Second pass on the same report. Taking the field's own padding off got rows from 57dp to 48dp and the operator said it was still too big — correctly, because 48dp was never the field's, it is Material's minimum touch target and every interactive component gets it. On a checklist that minimum IS the row height. It is the right floor for a control somebody has to find on a screen; it is the wrong one for a box that sits in a predictable column with an identical box directly above and below it, where a near miss ticks the neighbouring item — visible, and undone by tapping again. 36dp, provided to the row rather than hardcoded into the controls, so the checkbox and the delete × move together and nothing else in the app is affected.
This commit is contained in:
@@ -14,9 +14,11 @@ import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -136,38 +138,54 @@ private fun TaskBlock(
|
||||
onEnter: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = block.checked == true,
|
||||
onCheckedChange = { onChange(block.copy(checked = it)) },
|
||||
enabled = !readOnly,
|
||||
)
|
||||
BlockField(
|
||||
value = block.value,
|
||||
onValueChange = { onChange(block.copy(value = it)) },
|
||||
modifier = Modifier.weight(1f).focusRequester(requester),
|
||||
enabled = !readOnly,
|
||||
singleLine = true,
|
||||
textStyle =
|
||||
MaterialTheme.typography.bodyLarge.copy(
|
||||
// Struck through when done, matching the card and the web.
|
||||
textDecoration =
|
||||
if (block.checked == true) TextDecoration.LineThrough else null,
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
keyboardActions = KeyboardActions(onNext = { onEnter() }),
|
||||
)
|
||||
if (!readOnly) {
|
||||
IconButton(onClick = onDelete) {
|
||||
Icon(
|
||||
Icons.Filled.Close,
|
||||
contentDescription = stringResource(R.string.editor_remove_item),
|
||||
)
|
||||
// Material sizes every interactive component to a 48dp touch target, and on a
|
||||
// checklist that IS the row height — which is why six items filled a phone screen
|
||||
// even after the field's own padding came off.
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides ROW_TOUCH) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(
|
||||
checked = block.checked == true,
|
||||
onCheckedChange = { onChange(block.copy(checked = it)) },
|
||||
enabled = !readOnly,
|
||||
)
|
||||
BlockField(
|
||||
value = block.value,
|
||||
onValueChange = { onChange(block.copy(value = it)) },
|
||||
modifier = Modifier.weight(1f).focusRequester(requester),
|
||||
enabled = !readOnly,
|
||||
singleLine = true,
|
||||
textStyle =
|
||||
MaterialTheme.typography.bodyLarge.copy(
|
||||
// Struck through when done, matching the card and the web.
|
||||
textDecoration =
|
||||
if (block.checked == true) TextDecoration.LineThrough else null,
|
||||
),
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
keyboardActions = KeyboardActions(onNext = { onEnter() }),
|
||||
)
|
||||
if (!readOnly) {
|
||||
IconButton(onClick = onDelete) {
|
||||
Icon(
|
||||
Icons.Filled.Close,
|
||||
contentDescription = stringResource(R.string.editor_remove_item),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The touch target for a checklist row's controls.
|
||||
*
|
||||
* Material's floor is 48dp and this is deliberately under it. That floor is sized for
|
||||
* a control somebody has to find; a checklist box sits in a predictable column with an
|
||||
* identical box directly above and below, and the cost of a near miss is ticking the
|
||||
* neighbouring item — visible, and undone by tapping again. Trading twelve of those
|
||||
* dp for a list that fits on a screen is what was asked for, twice.
|
||||
*/
|
||||
private val ROW_TOUCH = 36.dp
|
||||
|
||||
/**
|
||||
* The field a block is typed into.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user