Set the month chip time apart from its title (#219)
This commit is contained in:
@@ -3,12 +3,20 @@ package de.jeanlucmakiola.calendula.ui.month
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
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.rememberTextMeasurer
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
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.domain.EventInstance
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
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.calendula.ui.common.formatTimeOfDay
|
||||||
import de.jeanlucmakiola.floret.locale.currentLocale
|
import de.jeanlucmakiola.floret.locale.currentLocale
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
@@ -44,6 +52,36 @@ private const val TITLE_SAMPLE = "notepad"
|
|||||||
private const val SAMPLE_HOUR = 12
|
private const val SAMPLE_HOUR = 12
|
||||||
private const val SAMPLE_MINUTE = 45
|
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
|
* 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
|
* 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 locale = currentLocale()
|
||||||
val sample = formatTimeOfDay(SAMPLE_HOUR, SAMPLE_MINUTE, LocalUse24HourFormat.current, locale)
|
val sample = formatTimeOfDay(SAMPLE_HOUR, SAMPLE_MINUTE, LocalUse24HourFormat.current, locale)
|
||||||
return remember(sample, style, density, measurer) {
|
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
|
with(density) { textPx.toDp() } + CHIP_CHROME
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ import de.jeanlucmakiola.calendula.ui.common.declinedDecoration
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.LocalDimCutoff
|
import de.jeanlucmakiola.calendula.ui.common.LocalDimCutoff
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
|
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
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.eventAccent
|
||||||
import de.jeanlucmakiola.calendula.ui.common.EventChipShape
|
import de.jeanlucmakiola.calendula.ui.common.EventChipShape
|
||||||
import de.jeanlucmakiola.calendula.ui.common.monthBarShape
|
import de.jeanlucmakiola.calendula.ui.common.monthBarShape
|
||||||
@@ -2471,11 +2472,13 @@ private fun MonthBar(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
||||||
val label = if (time != null) "$time $title" else title
|
|
||||||
val dimCutoff = LocalDimCutoff.current
|
val dimCutoff = LocalDimCutoff.current
|
||||||
val dimmed = dimCutoff != null && event.hasEnded(dimCutoff)
|
val dimmed = dimCutoff != null && event.hasEnded(dimCutoff)
|
||||||
val soften = LocalSoftenColors.current
|
val soften = LocalSoftenColors.current
|
||||||
val fill = eventFill(event.color, dark, soften)
|
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)
|
val moveAction = eventMoveAction(event)
|
||||||
// The source stays put as a ghost while its floating copy travels.
|
// The source stays put as a ghost while its floating copy travels.
|
||||||
val monthDrag = LocalMonthDrag.current
|
val monthDrag = LocalMonthDrag.current
|
||||||
@@ -2489,7 +2492,7 @@ private fun MonthBar(
|
|||||||
.background(fill, shape)
|
.background(fill, shape)
|
||||||
.padding(horizontal = MONTH_CHIP_TEXT_PADDING)
|
.padding(horizontal = MONTH_CHIP_TEXT_PADDING)
|
||||||
.semantics {
|
.semantics {
|
||||||
contentDescription = label
|
contentDescription = label.text
|
||||||
if (moveAction != null) customActions = listOf(moveAction)
|
if (moveAction != null) customActions = listOf(moveAction)
|
||||||
},
|
},
|
||||||
contentAlignment = Alignment.CenterStart,
|
contentAlignment = Alignment.CenterStart,
|
||||||
@@ -2501,7 +2504,7 @@ private fun MonthBar(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = titleOverflow.overflow,
|
overflow = titleOverflow.overflow,
|
||||||
softWrap = titleOverflow.softWrap,
|
softWrap = titleOverflow.softWrap,
|
||||||
color = eventInk(fill),
|
color = eventInk(fill, alpha = TITLE_INK_ALPHA),
|
||||||
textDecoration = declinedDecoration(event.isDeclined),
|
textDecoration = declinedDecoration(event.isDeclined),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package de.jeanlucmakiola.calendula.ui.month
|
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 com.google.common.truth.Truth.assertThat
|
||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||||
import kotlinx.datetime.DateTimeUnit
|
import kotlinx.datetime.DateTimeUnit
|
||||||
@@ -55,6 +57,26 @@ class MonthChipTimeTest {
|
|||||||
assertThat(time).isEqualTo("2:30 pm")
|
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
|
@Test
|
||||||
fun `an all-day chip never shows a time`() {
|
fun `an all-day chip never shows a time`() {
|
||||||
val time = monthChipTime(allDay(), continuesLeft = false, zone, is24Hour = true, locale)
|
val time = monthChipTime(allDay(), continuesLeft = false, zone, is24Hour = true, locale)
|
||||||
|
|||||||
Reference in New Issue
Block a user