Set the month chip time apart from its title (#219)

This commit is contained in:
2026-09-04 11:56:36 +02:00
parent f7e9990c4e
commit 065afdb2b8
3 changed files with 68 additions and 4 deletions

View File

@@ -3,12 +3,20 @@ package de.jeanlucmakiola.calendula.ui.month
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import de.jeanlucmakiola.calendula.domain.EventInstance
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
import de.jeanlucmakiola.calendula.ui.common.SECONDARY_INK_ALPHA
import de.jeanlucmakiola.calendula.ui.common.formatTimeOfDay
import de.jeanlucmakiola.floret.locale.currentLocale
import kotlinx.datetime.TimeZone
@@ -44,6 +52,36 @@ private const val TITLE_SAMPLE = "notepad"
private const val SAMPLE_HOUR = 12
private const val SAMPLE_MINUTE = 45
/**
* Ink for a chip's title, against [SECONDARY_INK_ALPHA] for the time in front of
* it — the same pairing the week and day blocks put a title and its time label
* in, so a chip that carries both reads the way a block does.
*/
internal const val TITLE_INK_ALPHA = 0.85f
/**
* How the time is set apart from the title it precedes (#219). labelSmall's
* medium weight steps down to regular and its 0.5sp tracking closes up, so the
* two read as a time and a title rather than as one run-on string. Both moves
* also narrow the prefix, which is worth most on the column that only just
* earned it.
*
* The ink is handed in rather than dimmed further: at this size a fainter grey
* would give up the contrast [SECONDARY_INK_ALPHA] exists to hold, so the
* weight carries the difference instead.
*/
private val TIME_SPAN = SpanStyle(fontWeight = FontWeight.Normal, letterSpacing = 0.sp)
/** A chip's text — its [time], set apart in [timeInk], before the [title]. */
internal fun monthChipLabel(time: String?, title: String, timeInk: Color): AnnotatedString =
buildAnnotatedString {
if (time != null) {
withStyle(TIME_SPAN.copy(color = timeInk)) { append(time) }
append(" ")
}
append(title)
}
/**
* The start time a chip shows before its title (#219), or null when it has none
* to show: an all-day chip never carries a time, and neither does a bar carried
@@ -78,7 +116,8 @@ internal fun rememberMonthTimeChipWidth(): Dp {
val locale = currentLocale()
val sample = formatTimeOfDay(SAMPLE_HOUR, SAMPLE_MINUTE, LocalUse24HourFormat.current, locale)
return remember(sample, style, density, measurer) {
val textPx = measurer.measure("$sample $TITLE_SAMPLE", style).size.width
val label = monthChipLabel(sample, TITLE_SAMPLE, Color.Unspecified)
val textPx = measurer.measure(label, style).size.width
with(density) { textPx.toDp() } + CHIP_CHROME
}
}

View File

@@ -147,6 +147,7 @@ import de.jeanlucmakiola.calendula.ui.common.declinedDecoration
import de.jeanlucmakiola.calendula.ui.common.LocalDimCutoff
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
import de.jeanlucmakiola.calendula.ui.common.SECONDARY_INK_ALPHA
import de.jeanlucmakiola.calendula.ui.common.eventAccent
import de.jeanlucmakiola.calendula.ui.common.EventChipShape
import de.jeanlucmakiola.calendula.ui.common.monthBarShape
@@ -2471,11 +2472,13 @@ private fun MonthBar(
null
}
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
val label = if (time != null) "$time $title" else title
val dimCutoff = LocalDimCutoff.current
val dimmed = dimCutoff != null && event.hasEnded(dimCutoff)
val soften = LocalSoftenColors.current
val fill = eventFill(event.color, dark, soften)
// The same title/secondary ink pairing the week and day blocks use, with
// the time on the quieter half.
val label = monthChipLabel(time, title, eventInk(fill, alpha = SECONDARY_INK_ALPHA))
val moveAction = eventMoveAction(event)
// The source stays put as a ghost while its floating copy travels.
val monthDrag = LocalMonthDrag.current
@@ -2489,7 +2492,7 @@ private fun MonthBar(
.background(fill, shape)
.padding(horizontal = MONTH_CHIP_TEXT_PADDING)
.semantics {
contentDescription = label
contentDescription = label.text
if (moveAction != null) customActions = listOf(moveAction)
},
contentAlignment = Alignment.CenterStart,
@@ -2501,7 +2504,7 @@ private fun MonthBar(
maxLines = 1,
overflow = titleOverflow.overflow,
softWrap = titleOverflow.softWrap,
color = eventInk(fill),
color = eventInk(fill, alpha = TITLE_INK_ALPHA),
textDecoration = declinedDecoration(event.isDeclined),
)
}

View File

@@ -1,5 +1,7 @@
package de.jeanlucmakiola.calendula.ui.month
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import com.google.common.truth.Truth.assertThat
import de.jeanlucmakiola.calendula.domain.EventInstance
import kotlinx.datetime.DateTimeUnit
@@ -55,6 +57,26 @@ class MonthChipTimeTest {
assertThat(time).isEqualTo("2:30 pm")
}
@Test
fun `the time is set apart from the title it precedes`() {
val label = monthChipLabel("09:05", "Standup", Color.Black)
assertThat(label.text).isEqualTo("09:05 Standup")
// Only the time is restyled: the title keeps labelSmall as the theme
// sets it, so the two read as separate things on one line (#219).
val spans = label.spanStyles
assertThat(spans).hasSize(1)
assertThat(spans.single().start).isEqualTo(0)
assertThat(spans.single().end).isEqualTo("09:05".length)
assertThat(spans.single().item.fontWeight).isEqualTo(FontWeight.Normal)
}
@Test
fun `a chip without a time is styled title and nothing else`() {
val label = monthChipLabel(null, "Standup", Color.Black)
assertThat(label.text).isEqualTo("Standup")
assertThat(label.spanStyles).isEmpty()
}
@Test
fun `an all-day chip never shows a time`() {
val time = monthChipTime(allDay(), continuesLeft = false, zone, is24Hour = true, locale)