The hour lines in week and day view become a per-hour grid, in the same visual language as the month grid: each hour is a rounded cell on `surfaceContainer`, separated by a 2dp gap that reveals `surface` behind it, so the boundary is negative space rather than a stroke drawn across the column. The column's Card takes `surface` and the container colour moves onto the cells. ### What changed - `HourLines.kt` → `HourGrid.kt`. `hourGridCells` draws 24 rounded rects in the same `drawBehind` the 23 lines used — still a background layer, so blocks keep their continuous coordinate space and keep spanning cells. - The cell corner is the event chip's 4dp rather than the month grid's 12dp: on a cell this size the 12dp out-rounds its own content. It is clamped to half the shorter side, so neither a fit-the-day sliver nor a narrow week column rounds into a lozenge. - The hour gutter's label lift is derived from the label's own line height instead of a hardcoded `-6.dp`, so the label straddles the seam at any font scale. This was the third defect the issue listed; the other two (a pixel-width stroke, and a "line" broken into seven segments by `COLUMN_GAP`) go away with the lines themselves. - The preference keeps its stored key and its string keys — only the English source text and the Kotlin names change (`showHourLines` → `showHourGrid`), as the issue asked. ### Deviation The issue expected block placement to be untouched. It isn't: a block gives up the same half-gap at each end that the cells do, otherwise an on-the-hour event overhangs the seam into its neighbours' cells and the grid reads as broken wherever there is an event. That inset follows through to the drag — the floating copy takes the same gap off so it stays exactly the size of the block it lifted off (or it has room for a time label the block had to drop, and the label appears on long-press and goes again on drop, #267), and the grab is measured off the block's placement rather than off where the grid seats it. An end cut at midnight keeps none of the inset and stays against the column edge, where `timedBlockCuts` already squares it off. ### Note for translators `settings_hour_lines` and its summary keep their keys but changed meaning, so the 12 `values-*` files still describe a separator line until Weblate re-syncs. Renaming the keys is not an option — stale keys in the translations are fatal. Closes #113 Supersedes #279, which was opened against `main` by mistake and has been undone there. Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/280
This commit is contained in:
@@ -95,9 +95,9 @@ class SettingsPrefsTest {
|
||||
@Test
|
||||
fun `hour lines default off and round-trips`(@TempDir tempDir: Path) = runTest {
|
||||
val prefs = SettingsPrefs(newDataStore(tempDir))
|
||||
assertThat(prefs.showHourLines.first()).isFalse()
|
||||
prefs.setShowHourLines(true)
|
||||
assertThat(prefs.showHourLines.first()).isTrue()
|
||||
assertThat(prefs.showHourGrid.first()).isFalse()
|
||||
prefs.setShowHourGrid(true)
|
||||
assertThat(prefs.showHourGrid.first()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
/**
|
||||
* The floating copy is the size of the block it lifted off, hour grid or not —
|
||||
* a copy a half-gap taller at each end has room for a time label the block
|
||||
* itself had to drop, so the label appears on long-press and goes on drop
|
||||
* (#267).
|
||||
*/
|
||||
class DragPieceBoundsTest {
|
||||
|
||||
/** A Regular 56dp hour on a 3x screen. */
|
||||
private val hourPx = 168f
|
||||
|
||||
/** The grid's half-gap, 1dp at 3x. */
|
||||
private val insetPx = 3f
|
||||
|
||||
private fun slice(
|
||||
startMin: Int,
|
||||
spanMin: Int,
|
||||
continuesBefore: Boolean = false,
|
||||
continuesAfter: Boolean = false,
|
||||
) = DragSlice(
|
||||
dayOffset = 0,
|
||||
startMin = startMin,
|
||||
spanMin = spanMin,
|
||||
continuesBefore = continuesBefore,
|
||||
continuesAfter = continuesAfter,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `with the grid off a piece keeps its raw placement`() {
|
||||
val bounds = dragPieceBounds(slice(startMin = 540, spanMin = 60), hourPx, insetPx = 0f)
|
||||
assertThat(bounds.top).isEqualTo(9 * hourPx)
|
||||
assertThat(bounds.height).isEqualTo(hourPx)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `with the grid on a piece gives up a half-gap at each end`() {
|
||||
val bounds = dragPieceBounds(slice(startMin = 540, spanMin = 60), hourPx, insetPx)
|
||||
assertThat(bounds.top).isEqualTo(9 * hourPx + insetPx)
|
||||
assertThat(bounds.height).isEqualTo(hourPx - insetPx * 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an end cut at midnight keeps none of the inset`() {
|
||||
val head = dragPieceBounds(slice(startMin = 1200, spanMin = 240, continuesAfter = true), hourPx, insetPx)
|
||||
assertThat(head.top).isEqualTo(20 * hourPx + insetPx)
|
||||
assertThat(head.height).isEqualTo(4 * hourPx - insetPx)
|
||||
|
||||
val tail = dragPieceBounds(slice(startMin = 0, spanMin = 480, continuesBefore = true), hourPx, insetPx)
|
||||
assertThat(tail.top).isEqualTo(0f)
|
||||
assertThat(tail.height).isEqualTo(8 * hourPx - insetPx)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a whole day cut at both ends fills its column`() {
|
||||
val bounds = dragPieceBounds(
|
||||
slice(startMin = 0, spanMin = 1440, continuesBefore = true, continuesAfter = true),
|
||||
hourPx,
|
||||
insetPx,
|
||||
)
|
||||
assertThat(bounds.top).isEqualTo(0f)
|
||||
assertThat(bounds.height).isEqualTo(24 * hourPx)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a very short piece is floored before the inset comes off`() {
|
||||
val bounds = dragPieceBounds(slice(startMin = 540, spanMin = 5), hourPx, insetPx)
|
||||
assertThat(bounds.height).isEqualTo(MIN_EVENT_FRACTION * hourPx - insetPx * 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an inset taller than the piece leaves no height at all`() {
|
||||
val bounds = dragPieceBounds(slice(startMin = 0, spanMin = 60), hourPx = 4f, insetPx = insetPx)
|
||||
assertThat(bounds.height).isEqualTo(0f)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class HourGridTest {
|
||||
|
||||
/** The hour cell's 4dp corner at a 3x density. */
|
||||
private val maxRadius = 12f
|
||||
|
||||
@Test
|
||||
fun `a tall cell rounds to the full radius`() {
|
||||
// A Regular 56dp hour on a 3x screen, in a phone-width day column.
|
||||
assertThat(hourCellRadiusPx(cellHeight = 162f, cellWidth = 900f, maxRadius = maxRadius))
|
||||
.isEqualTo(maxRadius)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a short cell rounds to half its height, never past a lozenge`() {
|
||||
// Fit-the-day on a small viewport: the hour is shorter than the radius.
|
||||
val radius = hourCellRadiusPx(cellHeight = 18f, cellWidth = 900f, maxRadius = maxRadius)
|
||||
assertThat(radius).isEqualTo(9f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a narrow cell rounds to half its width`() {
|
||||
// Seven columns on a small phone, so the cell is narrower than it is tall.
|
||||
assertThat(hourCellRadiusPx(cellHeight = 162f, cellWidth = 18f, maxRadius = maxRadius))
|
||||
.isEqualTo(9f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a degenerate cell asks for no radius at all`() {
|
||||
assertThat(hourCellRadiusPx(cellHeight = -4f, cellWidth = 900f, maxRadius = maxRadius))
|
||||
.isEqualTo(0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a seated block gives up the half-gap at an end of its own`() {
|
||||
assertThat(hourCellBlockInset(show = true, cut = false)).isEqualTo(HOUR_CELL_INSET)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an end cut at midnight stays against the column edge`() {
|
||||
assertThat(hourCellBlockInset(show = true, cut = true)).isEqualTo(0.dp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `with the grid off nothing is given up`() {
|
||||
assertThat(hourCellBlockInset(show = false, cut = false)).isEqualTo(0.dp)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user