From 68f851110f662e4a4fe759a30fc2a8876e76f7c7 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 26 Aug 2026 08:31:21 -0400 Subject: [PATCH] android: checklist rows were still 48dp of touch target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../fabledsword/thoughtsync/ui/BlockBody.kt | 72 ++++++++++++------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BlockBody.kt b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BlockBody.kt index c609eeb..c1b2ed7 100644 --- a/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BlockBody.kt +++ b/android/app/src/main/java/com/fabledsword/thoughtsync/ui/BlockBody.kt @@ -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. *