diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthGrid.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthGrid.kt index 23783c1..601f28c 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthGrid.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthGrid.kt @@ -36,16 +36,24 @@ internal fun mergeGaps(columns: Int, cellAt: (Int) -> GridCell?): L return cells } +/** What a cell of the single mixed lane shows: an event's bar, or a day's event count. */ +internal sealed interface LaneItem { + data class Bar(val event: EventInstance) : LaneItem + data class Count(val count: Int) : LaneItem +} + /** * One week of the grid seated with [laneCap] lanes, drawn [compact] when the * row is too short for a full one. [showsOverflow] is off when the week has no - * room for the "+N" row. + * room for the "+N" row; a [mixed] single lane then shows each day's count in + * place of its bar where the day has more than fits (#491). */ internal class WeekLayout( val week: MonthWeek, val laneCap: Int, val showsOverflow: Boolean = true, val compact: Boolean = false, + val mixed: Boolean = false, ) { private val seating: List = week.days.indices.map { week.seating(it, laneCap) } @@ -61,6 +69,16 @@ internal class WeekLayout( } } + /** The single lane of a [mixed] week, bars cut short where a day shows its count instead; null otherwise. */ + val mixedLane: List>? = if (!mixed) null else mergeGaps(week.days.size) { col -> + val day = seating[col] + if (day.hidden.isNotEmpty()) return@mergeGaps GridCell(LaneItem.Count(day.seated.size + day.hidden.size), 1) + val seat = day.seated.firstOrNull() ?: return@mergeGaps null + val spanEnd = if (seat.bar) week.spanAt(col, seat.lane)?.endCol ?: col else col + val end = (col + 1..spanEnd).firstOrNull { seating[it].hidden.isNotEmpty() }?.minus(1) ?: spanEnd + GridCell(LaneItem.Bar(seat.event), end - col + 1) + } + /** The "+N" row as counts and merged gaps; empty when everything fits. */ val overflow: List> = seating.map { it.hidden.size }.let { hidden -> if (hidden.none { it > 0 }) emptyList() @@ -85,12 +103,14 @@ internal fun lanesFitting(weekHeight: Dp, fontScale: Float): LaneFit { /** * Seats each week in [fit]'s lanes. A week whose events need more gives its - * last lane to the "+N" row instead. A compact lane has no room for that row, - * so it keeps its bars (#491). + * last lane to the "+N" row instead. A single lane has no room for that row, + * so it mixes bars and per-day counts (#491). */ internal fun fitWeeks(weeks: List, fit: LaneFit): List = weeks.map { week -> val lanes = fit.lanes - if (lanes == 0 || fit.compact) return@map WeekLayout(week, laneCap = lanes, showsOverflow = false, compact = fit.compact) + if (lanes <= 1) { + return@map WeekLayout(week, laneCap = lanes, showsOverflow = false, compact = fit.compact, mixed = lanes == 1) + } val needed = WeekLayout(week, UNCAPPED_LANES).usedLanes WeekLayout(week, if (needed <= lanes) lanes else lanes - 1) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthGridViews.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthGridViews.kt index 2ac5145..ce0a8d9 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthGridViews.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthGridViews.kt @@ -91,6 +91,21 @@ private fun weekViews( week.setViewVisibility(TODAY_IDS[col], if (isToday) View.VISIBLE else View.GONE) if (isToday) week.setImageTint(context, TODAY_IDS[col], colors.primary) } + layout.mixedLane?.let { cells -> + val lane = laneViews(context, layout.compact) + cells.forEach { cell -> + lane.addView( + R.id.lane, + when (val item = cell.item) { + null -> gap(context, cell.cols) + is LaneItem.Bar -> bar(context, item.event, cell.cols, soften, mode) + is LaneItem.Count -> more(context, item.count, colors) + }, + ) + } + week.addView(R.id.week_lanes, lane) + return week + } layout.lanes.forEach { cells -> val lane = laneViews(context, layout.compact) cells.forEach { cell -> @@ -106,10 +121,7 @@ private fun weekViews( if (count == null) { lane.addView(R.id.lane, gap(context, cell.cols)) } else { - val more = RemoteViews(context.packageName, R.layout.widget_month_more) - more.setTextViewText(R.id.more, "+$count") - more.setTextColor(context, R.id.more, colors.onSurfaceVariant) - lane.addView(R.id.lane, more) + lane.addView(R.id.lane, more(context, count, colors)) } } week.addView(R.id.week_lanes, lane) @@ -117,6 +129,12 @@ private fun weekViews( return week } +private fun more(context: Context, count: Int, colors: GridColors) = + RemoteViews(context.packageName, R.layout.widget_month_more).apply { + setTextViewText(R.id.more, "+$count") + setTextColor(context, R.id.more, colors.onSurfaceVariant) + } + private fun laneViews(context: Context, compact: Boolean = false) = RemoteViews(context.packageName, if (compact) R.layout.widget_month_lane_compact else R.layout.widget_month_lane) .apply { removeAllViews(R.id.lane) } diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/widget/month/MonthGridTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/widget/month/MonthGridTest.kt index c336b50..b1a647f 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/widget/month/MonthGridTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/widget/month/MonthGridTest.kt @@ -75,16 +75,24 @@ class MonthGridTest { } @Test - fun `a single full lane still folds a busy week into the count`() { + fun `a single lane shows counts on busy days`() { val layouts = fitWeeks(busyWeeks(perDay = 5), LaneFit(1)) - assertThat(layouts.all { it.laneCap == 0 && it.showsOverflow && it.overflow.isNotEmpty() }).isTrue() + assertThat(layouts.all { it.mixedLane == List(7) { GridCell(LaneItem.Count(5), 1) } }).isTrue() } @Test - fun `a compact lane keeps its bars in a busy week`() { - val layouts = fitWeeks(busyWeeks(perDay = 5), LaneFit(1, compact = true)) - assertThat(layouts.all { it.laneCap == 1 && it.lanes.size == 1 && it.compact }).isTrue() - assertThat(layouts.none { it.showsOverflow }).isTrue() + fun `a single lane keeps bars on days that fit`() { + val lone = timedEvent(monday.plus(1, DateTimeUnit.DAY), 9, zone = zone, id = 1, title = "Lone") + val busy = (2L..4L).map { timedEvent(monday.plus(3, DateTimeUnit.DAY), it.toInt() + 8, zone = zone, id = it, title = "B$it") } + val week = fitWeeks(weeks(listOf(lone) + busy), LaneFit(1, compact = true))[1] + assertThat(week.compact).isTrue() + assertThat(week.mixedLane).containsExactly( + GridCell(null, 1), + GridCell(LaneItem.Bar(lone), 1), + GridCell(null, 1), + GridCell(LaneItem.Count(3), 1), + GridCell(null, 3), + ).inOrder() } @Test