Checklists in the body, colour from tags, and commit-derived CalVer #4

Merged
bvandeusen merged 73 commits from dev into main 2026-08-29 13:39:45 -04:00
Showing only changes of commit 68f851110f - Show all commits
@@ -14,9 +14,11 @@ import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -136,38 +138,54 @@ private fun TaskBlock(
onEnter: () -> Unit, onEnter: () -> Unit,
onDelete: () -> Unit, onDelete: () -> Unit,
) { ) {
Row(verticalAlignment = Alignment.CenterVertically) { // Material sizes every interactive component to a 48dp touch target, and on a
Checkbox( // checklist that IS the row height — which is why six items filled a phone screen
checked = block.checked == true, // even after the field's own padding came off.
onCheckedChange = { onChange(block.copy(checked = it)) }, CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides ROW_TOUCH) {
enabled = !readOnly, Row(verticalAlignment = Alignment.CenterVertically) {
) Checkbox(
BlockField( checked = block.checked == true,
value = block.value, onCheckedChange = { onChange(block.copy(checked = it)) },
onValueChange = { onChange(block.copy(value = it)) }, enabled = !readOnly,
modifier = Modifier.weight(1f).focusRequester(requester), )
enabled = !readOnly, BlockField(
singleLine = true, value = block.value,
textStyle = onValueChange = { onChange(block.copy(value = it)) },
MaterialTheme.typography.bodyLarge.copy( modifier = Modifier.weight(1f).focusRequester(requester),
// Struck through when done, matching the card and the web. enabled = !readOnly,
textDecoration = singleLine = true,
if (block.checked == true) TextDecoration.LineThrough else null, textStyle =
), MaterialTheme.typography.bodyLarge.copy(
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), // Struck through when done, matching the card and the web.
keyboardActions = KeyboardActions(onNext = { onEnter() }), textDecoration =
) if (block.checked == true) TextDecoration.LineThrough else null,
if (!readOnly) { ),
IconButton(onClick = onDelete) { keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
Icon( keyboardActions = KeyboardActions(onNext = { onEnter() }),
Icons.Filled.Close, )
contentDescription = stringResource(R.string.editor_remove_item), 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. * The field a block is typed into.
* *