fix(widget): scale the agenda widget with its size (#51) #88
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- The "Upcoming" agenda widget now scales its text and rows to the size you give
|
||||
it. Previously it was laid out once for the smallest size and simply stretched
|
||||
when enlarged, so the text stayed small no matter how big you made the widget.
|
||||
Now a bigger widget gets bigger, more readable type and roomier rows, while the
|
||||
default size looks exactly as before — no new setting; it follows the size you
|
||||
already chose ([#51]).
|
||||
|
||||
## [2.16.0] — 2026-07-17
|
||||
|
||||
### Added
|
||||
@@ -1038,5 +1046,6 @@ automatically, with zero telemetry and no internet permission.
|
||||
[#47]: https://codeberg.org/jlmakiola/calendula/issues/47
|
||||
[#48]: https://codeberg.org/jlmakiola/calendula/issues/48
|
||||
[#49]: https://codeberg.org/jlmakiola/calendula/issues/49
|
||||
[#51]: https://codeberg.org/jlmakiola/calendula/issues/51
|
||||
[#52]: https://codeberg.org/jlmakiola/calendula/issues/52
|
||||
[#60]: https://codeberg.org/jlmakiola/calendula/issues/60
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package de.jeanlucmakiola.calendula.widget.agenda
|
||||
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
/**
|
||||
* Size tiers the agenda widget scales its typography and row metrics across (#51).
|
||||
*
|
||||
* The widget uses [androidx.glance.appwidget.SizeMode.Exact], so the composition
|
||||
* sees the widget's live size via `LocalSize.current`; [scaleFor] buckets that
|
||||
* into one of these tiers and [metricsFor] returns the sizes to draw with. Kept
|
||||
* in a pure, Glance-free file (only `compose.ui.unit`) so the bucketing and the
|
||||
* "default size is unchanged" invariant are covered by plain JVM tests.
|
||||
*/
|
||||
internal enum class AgendaScale { COMPACT, REGULAR, LARGE, XLARGE }
|
||||
|
||||
/**
|
||||
* Every size the agenda widget varies by tier. Values that don't need to grow
|
||||
* (horizontal paddings, the 5.dp stripe width, corner radii) stay inline literals
|
||||
* in the widget rather than routing through here.
|
||||
*/
|
||||
internal data class AgendaMetrics(
|
||||
val title: TextUnit, // header "Upcoming"
|
||||
val dayHeader: TextUnit, // day label ("Today · …")
|
||||
val eventTitle: TextUnit, // event title line
|
||||
val eventTime: TextUnit, // time/location line
|
||||
val placeholder: TextUnit, // "no more events today" (#35)
|
||||
val message: TextUnit, // empty/permission centred message
|
||||
val stripeH: Dp, // coloured event stripe height
|
||||
val iconImage: Dp, // header action icon glyph
|
||||
val iconBox: Dp, // header action touch target
|
||||
val rowVPad: Dp, // event row vertical padding
|
||||
)
|
||||
|
||||
/**
|
||||
* [AgendaMetrics] per tier. [AgendaScale.COMPACT] reproduces the widget's original
|
||||
* hardcoded constants **verbatim** — so at the default/small size nothing changes;
|
||||
* a JVM test pins this against the baseline. Larger tiers scale up by roughly
|
||||
* 1.12× / 1.28× / 1.45× (starting points to refine on-device).
|
||||
*/
|
||||
internal fun metricsFor(scale: AgendaScale): AgendaMetrics = when (scale) {
|
||||
AgendaScale.COMPACT -> AgendaMetrics(
|
||||
title = 16.sp,
|
||||
dayHeader = 13.sp,
|
||||
eventTitle = 14.sp,
|
||||
eventTime = 12.sp,
|
||||
placeholder = 14.sp,
|
||||
message = 14.sp,
|
||||
stripeH = 36.dp,
|
||||
iconImage = 22.dp,
|
||||
iconBox = 40.dp,
|
||||
rowVPad = 4.dp,
|
||||
)
|
||||
AgendaScale.REGULAR -> AgendaMetrics(
|
||||
title = 18.sp,
|
||||
dayHeader = 14.sp,
|
||||
eventTitle = 15.sp,
|
||||
eventTime = 13.sp,
|
||||
placeholder = 15.sp,
|
||||
message = 15.sp,
|
||||
stripeH = 40.dp,
|
||||
iconImage = 24.dp,
|
||||
iconBox = 44.dp,
|
||||
rowVPad = 5.dp,
|
||||
)
|
||||
AgendaScale.LARGE -> AgendaMetrics(
|
||||
title = 20.sp,
|
||||
dayHeader = 16.sp,
|
||||
eventTitle = 17.sp,
|
||||
eventTime = 14.sp,
|
||||
placeholder = 16.sp,
|
||||
message = 16.sp,
|
||||
stripeH = 46.dp,
|
||||
iconImage = 26.dp,
|
||||
iconBox = 48.dp,
|
||||
rowVPad = 6.dp,
|
||||
)
|
||||
AgendaScale.XLARGE -> AgendaMetrics(
|
||||
title = 22.sp,
|
||||
dayHeader = 18.sp,
|
||||
eventTitle = 19.sp,
|
||||
eventTime = 15.sp,
|
||||
placeholder = 18.sp,
|
||||
message = 18.sp,
|
||||
stripeH = 52.dp,
|
||||
iconImage = 28.dp,
|
||||
iconBox = 52.dp,
|
||||
rowVPad = 8.dp,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Buckets a live widget size into an [AgendaScale] by **width**.
|
||||
*
|
||||
* Width is the right axis: it governs how much of a title fits on a row, so it's
|
||||
* what should drive type size. Height only decides how many rows are visible — a
|
||||
* tall, narrow widget wants *more events*, not bigger text — so it never raises
|
||||
* the tier. It does act as a **cap**, though: a very short widget is stepped back
|
||||
* down so it can't keep oversized type in a sliver of space.
|
||||
*
|
||||
* The thresholds are spread across the width range a phone can actually produce
|
||||
* (~180dp up to roughly the screen width) rather than over a theoretical range, so
|
||||
* the tiers are reachable in practice. Calibrated on-device (Pixel / Nova): a
|
||||
* compact 222dp-wide widget stays COMPACT (the app's baseline, unchanged) and a
|
||||
* large 378dp-wide one reaches LARGE. XLARGE is reserved for genuinely wide
|
||||
* surfaces — tablets, foldables, landscape — where the extra size reads well.
|
||||
*/
|
||||
internal fun scaleFor(size: DpSize): AgendaScale {
|
||||
val byWidth = when {
|
||||
size.width < 260.dp -> AgendaScale.COMPACT
|
||||
size.width < 330.dp -> AgendaScale.REGULAR
|
||||
size.width < 420.dp -> AgendaScale.LARGE
|
||||
else -> AgendaScale.XLARGE
|
||||
}
|
||||
// Height can only ever pull the tier *down*, never push it up: a squashed
|
||||
// widget would otherwise keep the big type its width earned and look absurd
|
||||
// in the little space left. Keeping this a cap (rather than a second scaling
|
||||
// axis) is what preserves "tall and narrow shows more events, not bigger text".
|
||||
val heightCap = when {
|
||||
size.height < 220.dp -> AgendaScale.COMPACT
|
||||
size.height < 320.dp -> AgendaScale.REGULAR
|
||||
size.height < 420.dp -> AgendaScale.LARGE
|
||||
else -> AgendaScale.XLARGE
|
||||
}
|
||||
return minOf(byWidth, heightCap)
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import android.content.res.Configuration
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.glance.ColorFilter
|
||||
@@ -14,9 +13,11 @@ import androidx.glance.GlanceModifier
|
||||
import androidx.glance.GlanceTheme
|
||||
import androidx.glance.Image
|
||||
import androidx.glance.ImageProvider
|
||||
import androidx.glance.LocalSize
|
||||
import androidx.glance.action.ActionParameters
|
||||
import androidx.glance.action.clickable
|
||||
import androidx.glance.appwidget.GlanceAppWidget
|
||||
import androidx.glance.appwidget.SizeMode
|
||||
import androidx.glance.appwidget.action.ActionCallback
|
||||
import androidx.glance.appwidget.action.actionRunCallback
|
||||
import androidx.glance.appwidget.action.actionStartActivity
|
||||
@@ -107,6 +108,13 @@ class AgendaWidget : GlanceAppWidget() {
|
||||
|
||||
override val stateDefinition = PreferencesGlanceStateDefinition
|
||||
|
||||
// Exact (not Responsive) so a single copy of the day/event list is laid out at
|
||||
// the widget's live size — Responsive would replicate the whole LazyColumn per
|
||||
// declared tier. The composition reads LocalSize.current and scales type/rows
|
||||
// from it ([scaleFor]/[metricsFor]); at the default size that resolves to
|
||||
// COMPACT, i.e. the layout is unchanged (#51).
|
||||
override val sizeMode = SizeMode.Exact
|
||||
|
||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||
val data = context.loadAgendaWidgetData()
|
||||
val dark = (context.resources.configuration.uiMode and
|
||||
@@ -136,16 +144,19 @@ private sealed interface AgendaRow {
|
||||
|
||||
@Composable
|
||||
private fun AgendaWidgetBody(data: AgendaWidgetData, dark: Boolean) {
|
||||
// Type and row metrics scale with the widget's live size (SizeMode.Exact); a
|
||||
// short/compact widget resolves to COMPACT, leaving the layout unchanged (#51).
|
||||
val metrics = metricsFor(scaleFor(LocalSize.current))
|
||||
Column(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxSize()
|
||||
.background(GlanceTheme.colors.surface)
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
) {
|
||||
AgendaHeader()
|
||||
AgendaHeader(metrics)
|
||||
Spacer(GlanceModifier.height(4.dp))
|
||||
when (data) {
|
||||
AgendaWidgetData.NeedsPermission -> WidgetMessage(R.string.widget_needs_permission)
|
||||
AgendaWidgetData.NeedsPermission -> WidgetMessage(R.string.widget_needs_permission, metrics)
|
||||
is AgendaWidgetData.Ready -> {
|
||||
// Range read reactively from per-instance Glance state (falls back
|
||||
// to the saved pref for a freshly placed widget), then the wide
|
||||
@@ -179,7 +190,7 @@ private fun AgendaWidgetBody(data: AgendaWidgetData, dark: Boolean) {
|
||||
enabled = showToday,
|
||||
)
|
||||
if (visibleDays.isEmpty()) {
|
||||
WidgetMessage(R.string.agenda_empty_title)
|
||||
WidgetMessage(R.string.agenda_empty_title, metrics)
|
||||
} else {
|
||||
val rows = buildList {
|
||||
visibleDays.forEach { day ->
|
||||
@@ -194,8 +205,8 @@ private fun AgendaWidgetBody(data: AgendaWidgetData, dark: Boolean) {
|
||||
LazyColumn(modifier = GlanceModifier.fillMaxSize()) {
|
||||
items(rows.size) { index ->
|
||||
when (val row = rows[index]) {
|
||||
is AgendaRow.Header -> DayHeaderRow(row.date, row.today)
|
||||
is AgendaRow.Placeholder -> PlaceholderRow(row.date)
|
||||
is AgendaRow.Header -> DayHeaderRow(row.date, row.today, metrics)
|
||||
is AgendaRow.Placeholder -> PlaceholderRow(row.date, metrics)
|
||||
is AgendaRow.Event -> EventRow(
|
||||
event = row.event,
|
||||
day = row.date,
|
||||
@@ -204,6 +215,7 @@ private fun AgendaWidgetBody(data: AgendaWidgetData, dark: Boolean) {
|
||||
is24Hour = data.is24Hour,
|
||||
dimmed = pastDisplay == PastEventDisplay.DIM &&
|
||||
row.event.hasEnded(data.now),
|
||||
metrics = metrics,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -215,7 +227,7 @@ private fun AgendaWidgetBody(data: AgendaWidgetData, dark: Boolean) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AgendaHeader() {
|
||||
private fun AgendaHeader(metrics: AgendaMetrics) {
|
||||
val context = androidx.glance.LocalContext.current
|
||||
Row(
|
||||
modifier = GlanceModifier.fillMaxWidth().padding(horizontal = 4.dp),
|
||||
@@ -227,7 +239,7 @@ private fun AgendaHeader() {
|
||||
text = context.getString(R.string.widget_agenda_title),
|
||||
style = TextStyle(
|
||||
color = GlanceTheme.colors.primary,
|
||||
fontSize = 16.sp,
|
||||
fontSize = metrics.title,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
modifier = GlanceModifier
|
||||
@@ -240,6 +252,7 @@ private fun AgendaHeader() {
|
||||
resId = R.drawable.ic_widget_refresh,
|
||||
contentDescription = context.getString(R.string.widget_refresh),
|
||||
onClick = GlanceModifier.clickable(actionRunCallback<RefreshAgendaAction>()),
|
||||
metrics = metrics,
|
||||
)
|
||||
IconButton(
|
||||
resId = R.drawable.ic_widget_add,
|
||||
@@ -249,34 +262,40 @@ private fun AgendaHeader() {
|
||||
MainActivity.openCreateIntent(context, today(systemZone())),
|
||||
),
|
||||
),
|
||||
metrics = metrics,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IconButton(resId: Int, contentDescription: String, onClick: GlanceModifier) {
|
||||
private fun IconButton(
|
||||
resId: Int,
|
||||
contentDescription: String,
|
||||
onClick: GlanceModifier,
|
||||
metrics: AgendaMetrics,
|
||||
) {
|
||||
Box(
|
||||
modifier = GlanceModifier.size(40.dp).then(onClick),
|
||||
modifier = GlanceModifier.size(metrics.iconBox).then(onClick),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Image(
|
||||
provider = ImageProvider(resId),
|
||||
contentDescription = contentDescription,
|
||||
colorFilter = ColorFilter.tint(GlanceTheme.colors.onSurfaceVariant),
|
||||
modifier = GlanceModifier.size(22.dp),
|
||||
modifier = GlanceModifier.size(metrics.iconImage),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DayHeaderRow(date: LocalDate, today: LocalDate) {
|
||||
private fun DayHeaderRow(date: LocalDate, today: LocalDate, metrics: AgendaMetrics) {
|
||||
val context = androidx.glance.LocalContext.current
|
||||
Text(
|
||||
text = agendaDayLabel(context, date, today),
|
||||
style = TextStyle(
|
||||
color = if (date == today) GlanceTheme.colors.primary
|
||||
else GlanceTheme.colors.onSurfaceVariant,
|
||||
fontSize = 13.sp,
|
||||
fontSize = metrics.dayHeader,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
modifier = GlanceModifier
|
||||
@@ -296,11 +315,11 @@ private fun DayHeaderRow(date: LocalDate, today: LocalDate) {
|
||||
* plain too (a stripe + text, not cards), so a card here would look out of place.
|
||||
*/
|
||||
@Composable
|
||||
private fun PlaceholderRow(date: LocalDate) {
|
||||
private fun PlaceholderRow(date: LocalDate, metrics: AgendaMetrics) {
|
||||
val context = androidx.glance.LocalContext.current
|
||||
Text(
|
||||
text = context.getString(R.string.agenda_no_more_today),
|
||||
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = 14.sp),
|
||||
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = metrics.placeholder),
|
||||
modifier = GlanceModifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 19.dp, end = 8.dp, top = 2.dp, bottom = 6.dp)
|
||||
@@ -320,6 +339,7 @@ private fun EventRow(
|
||||
soften: Boolean,
|
||||
is24Hour: Boolean,
|
||||
dimmed: Boolean,
|
||||
metrics: AgendaMetrics,
|
||||
) {
|
||||
val context = androidx.glance.LocalContext.current
|
||||
val title = event.title.ifBlank { context.getString(R.string.event_untitled) }
|
||||
@@ -332,7 +352,7 @@ private fun EventRow(
|
||||
Row(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 4.dp, vertical = 4.dp)
|
||||
.padding(horizontal = 4.dp, vertical = metrics.rowVPad)
|
||||
.clickable(
|
||||
actionStartActivity(
|
||||
MainActivity.openEventIntent(
|
||||
@@ -349,7 +369,7 @@ private fun EventRow(
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.width(5.dp)
|
||||
.height(36.dp)
|
||||
.height(metrics.stripeH)
|
||||
.cornerRadius(3.dp)
|
||||
.background(stripeColor),
|
||||
) {}
|
||||
@@ -358,19 +378,19 @@ private fun EventRow(
|
||||
Text(
|
||||
text = title,
|
||||
maxLines = 1,
|
||||
style = TextStyle(color = titleColor, fontSize = 14.sp),
|
||||
style = TextStyle(color = titleColor, fontSize = metrics.eventTitle),
|
||||
)
|
||||
Text(
|
||||
text = eventTimeSummary(context, event, day, is24Hour),
|
||||
maxLines = 1,
|
||||
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = 12.sp),
|
||||
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = metrics.eventTime),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WidgetMessage(resId: Int) {
|
||||
private fun WidgetMessage(resId: Int, metrics: AgendaMetrics) {
|
||||
val context = androidx.glance.LocalContext.current
|
||||
Box(
|
||||
modifier = GlanceModifier.fillMaxSize().padding(16.dp),
|
||||
@@ -378,7 +398,7 @@ private fun WidgetMessage(resId: Int) {
|
||||
) {
|
||||
Text(
|
||||
text = context.getString(resId),
|
||||
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = 14.sp),
|
||||
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = metrics.message),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package de.jeanlucmakiola.calendula.widget.agenda
|
||||
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class AgendaScaleTest {
|
||||
|
||||
// --- scaleFor: bucketing -------------------------------------------------
|
||||
|
||||
@Test
|
||||
fun `the on-device calibration points map to their tiers`() {
|
||||
// The two sizes measured on-device: the compact widget stays COMPACT (the
|
||||
// baseline, unchanged), the large one steps up to LARGE — not XLARGE,
|
||||
// which read as too big on a phone (#51).
|
||||
assertThat(scaleFor(DpSize(222.dp, 270.dp))).isEqualTo(AgendaScale.COMPACT)
|
||||
assertThat(scaleFor(DpSize(378.dp, 672.dp))).isEqualTo(AgendaScale.LARGE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `width buckets into the four tiers`() {
|
||||
// Tall enough that the height cap never binds, isolating the width rule.
|
||||
val h = 500.dp
|
||||
assertThat(scaleFor(DpSize(180.dp, h))).isEqualTo(AgendaScale.COMPACT)
|
||||
assertThat(scaleFor(DpSize(259.dp, h))).isEqualTo(AgendaScale.COMPACT)
|
||||
assertThat(scaleFor(DpSize(260.dp, h))).isEqualTo(AgendaScale.REGULAR)
|
||||
assertThat(scaleFor(DpSize(329.dp, h))).isEqualTo(AgendaScale.REGULAR)
|
||||
assertThat(scaleFor(DpSize(330.dp, h))).isEqualTo(AgendaScale.LARGE)
|
||||
assertThat(scaleFor(DpSize(419.dp, h))).isEqualTo(AgendaScale.LARGE)
|
||||
assertThat(scaleFor(DpSize(420.dp, h))).isEqualTo(AgendaScale.XLARGE)
|
||||
assertThat(scaleFor(DpSize(900.dp, h))).isEqualTo(AgendaScale.XLARGE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extra height never raises the tier`() {
|
||||
// Height decides how many rows are visible, not how big they are: a tall,
|
||||
// narrow widget wants more events, not bigger text.
|
||||
val short = scaleFor(DpSize(222.dp, 200.dp))
|
||||
val tall = scaleFor(DpSize(222.dp, 900.dp))
|
||||
assertThat(short).isEqualTo(AgendaScale.COMPACT)
|
||||
assertThat(tall).isEqualTo(AgendaScale.COMPACT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a squashed widget is stepped back down`() {
|
||||
// Same (wide) width, shrinking height: the tier must walk down rather than
|
||||
// keep big type in a sliver of space.
|
||||
val wide = 378.dp
|
||||
assertThat(scaleFor(DpSize(wide, 672.dp))).isEqualTo(AgendaScale.LARGE)
|
||||
assertThat(scaleFor(DpSize(wide, 400.dp))).isEqualTo(AgendaScale.LARGE)
|
||||
assertThat(scaleFor(DpSize(wide, 300.dp))).isEqualTo(AgendaScale.REGULAR)
|
||||
assertThat(scaleFor(DpSize(wide, 200.dp))).isEqualTo(AgendaScale.COMPACT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the height cap never exceeds what the width earned`() {
|
||||
// A tall but narrow widget stays at its width's tier — the cap only ever
|
||||
// lowers, so a huge height can't promote a 222dp-wide widget.
|
||||
assertThat(scaleFor(DpSize(222.dp, 900.dp))).isEqualTo(AgendaScale.COMPACT)
|
||||
assertThat(scaleFor(DpSize(300.dp, 900.dp))).isEqualTo(AgendaScale.REGULAR)
|
||||
}
|
||||
|
||||
// --- metricsFor: the "default unchanged" regression guard -----------------
|
||||
|
||||
@Test
|
||||
fun `COMPACT metrics equal the widget's original constants`() {
|
||||
// If this fails, a default-sized agenda widget no longer looks as it did.
|
||||
val m = metricsFor(AgendaScale.COMPACT)
|
||||
assertThat(m.title).isEqualTo(16.sp)
|
||||
assertThat(m.dayHeader).isEqualTo(13.sp)
|
||||
assertThat(m.eventTitle).isEqualTo(14.sp)
|
||||
assertThat(m.eventTime).isEqualTo(12.sp)
|
||||
assertThat(m.placeholder).isEqualTo(14.sp)
|
||||
assertThat(m.message).isEqualTo(14.sp)
|
||||
assertThat(m.stripeH).isEqualTo(36.dp)
|
||||
assertThat(m.iconImage).isEqualTo(22.dp)
|
||||
assertThat(m.iconBox).isEqualTo(40.dp)
|
||||
assertThat(m.rowVPad).isEqualTo(4.dp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type sizes are non-decreasing across the tiers`() {
|
||||
val tiers = listOf(
|
||||
AgendaScale.COMPACT,
|
||||
AgendaScale.REGULAR,
|
||||
AgendaScale.LARGE,
|
||||
AgendaScale.XLARGE,
|
||||
).map(::metricsFor)
|
||||
|
||||
tiers.zipWithNext { small, big ->
|
||||
assertThat(big.title.value).isAtLeast(small.title.value)
|
||||
assertThat(big.dayHeader.value).isAtLeast(small.dayHeader.value)
|
||||
assertThat(big.eventTitle.value).isAtLeast(small.eventTitle.value)
|
||||
assertThat(big.eventTime.value).isAtLeast(small.eventTime.value)
|
||||
assertThat(big.placeholder.value).isAtLeast(small.placeholder.value)
|
||||
assertThat(big.message.value).isAtLeast(small.message.value)
|
||||
assertThat(big.stripeH.value).isAtLeast(small.stripeH.value)
|
||||
assertThat(big.iconImage.value).isAtLeast(small.iconImage.value)
|
||||
assertThat(big.iconBox.value).isAtLeast(small.iconBox.value)
|
||||
assertThat(big.rowVPad.value).isAtLeast(small.rowVPad.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user