Compare commits
3 Commits
11adfd66bc
...
renovate/k
| Author | SHA1 | Date | |
|---|---|---|---|
| acdf4d82ea | |||
|
|
66dbdc8913 | ||
|
|
6a125b7c73 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.18.1] — 2026-08-06
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Tapping an empty slot in week or day view creates the event at the hour you
|
||||||
|
tapped, after the timeline has been zoomed. Pinching the day taller or shorter
|
||||||
|
— or changing **Hour height** in Settings — left the tap still being measured
|
||||||
|
against the old spacing, so the new event landed at some other hour entirely
|
||||||
|
([#148]).
|
||||||
|
|
||||||
## [2.18.0] — 2026-07-31
|
## [2.18.0] — 2026-07-31
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -1323,3 +1332,4 @@ automatically, with zero telemetry and no internet permission.
|
|||||||
[#57]: https://codeberg.org/jlmakiola/calendula/issues/57
|
[#57]: https://codeberg.org/jlmakiola/calendula/issues/57
|
||||||
[#56]: https://codeberg.org/jlmakiola/calendula/issues/56
|
[#56]: https://codeberg.org/jlmakiola/calendula/issues/56
|
||||||
[#114]: https://codeberg.org/jlmakiola/calendula/issues/114
|
[#114]: https://codeberg.org/jlmakiola/calendula/issues/114
|
||||||
|
[#148]: https://codeberg.org/jlmakiola/calendula/issues/148
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ android {
|
|||||||
// which builds this version and then creates the matching vX.Y.Z tag +
|
// which builds this version and then creates the matching vX.Y.Z tag +
|
||||||
// release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 +
|
// release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 +
|
||||||
// PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md.
|
// PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md.
|
||||||
versionCode = 21800
|
versionCode = 21801
|
||||||
versionName = "2.18.0"
|
versionName = "2.18.1"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,18 @@ fun TimelineScale.hourHeight(viewportHeight: Dp): Dp = when (this) {
|
|||||||
.coerceAtLeast(fillHourHeight(viewportHeight))
|
.coerceAtLeast(fillHourHeight(viewportHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minute of the day a tap [offsetY] px down a timeline column means, snapped
|
||||||
|
* to the hour it landed in.
|
||||||
|
*
|
||||||
|
* [hourPx] must be the hour height the column is drawing at *now* — a tap
|
||||||
|
* detector that captured it when it was installed maps taps to a pre-pinch grid
|
||||||
|
* (#148). A zero or negative height (a viewport measured at nothing) has no grid
|
||||||
|
* to read, so it answers midnight rather than dividing by it.
|
||||||
|
*/
|
||||||
|
fun tappedMinuteOfDay(offsetY: Float, hourPx: Float): Int =
|
||||||
|
if (hourPx <= 0f) 0 else (offsetY / hourPx).toInt().coerceIn(0, 23) * 60
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortest an event block may render, as a fraction of an hour. Blocks keep a
|
* Shortest an event block may render, as a fraction of an hour. Blocks keep a
|
||||||
* floor so a 15-minute event stays tappable, but the floor scales with the hour
|
* floor so a 15-minute event stays tappable, but the floor scales with the hour
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -96,6 +97,7 @@ import de.jeanlucmakiola.calendula.ui.common.rememberTimelinePinchZoom
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.formatHourLabel
|
import de.jeanlucmakiola.calendula.ui.common.formatHourLabel
|
||||||
import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
||||||
import de.jeanlucmakiola.calendula.ui.common.hourSeparatorLines
|
import de.jeanlucmakiola.calendula.ui.common.hourSeparatorLines
|
||||||
|
import de.jeanlucmakiola.calendula.ui.common.tappedMinuteOfDay
|
||||||
import de.jeanlucmakiola.calendula.ui.week.TimedBlock
|
import de.jeanlucmakiola.calendula.ui.week.TimedBlock
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -577,6 +579,12 @@ private fun DayColumnCard(
|
|||||||
val hourPx = with(LocalDensity.current) { hourHeight.toPx() }
|
val hourPx = with(LocalDensity.current) { hourHeight.toPx() }
|
||||||
val showHourLines = LocalShowHourLines.current
|
val showHourLines = LocalShowHourLines.current
|
||||||
val hourLineColor = MaterialTheme.colorScheme.outlineVariant
|
val hourLineColor = MaterialTheme.colorScheme.outlineVariant
|
||||||
|
// The tap detector outlives the composition that installed it — a pinch or a
|
||||||
|
// Settings change moves the hour height without restarting it — so it reads
|
||||||
|
// the height and the callback through state handles instead of capturing
|
||||||
|
// them, or taps land on the scale the column had before the zoom (#148).
|
||||||
|
val currentHourPx = rememberUpdatedState(hourPx)
|
||||||
|
val currentOnCreateAt = rememberUpdatedState(onCreateAt)
|
||||||
Card(
|
Card(
|
||||||
// Plain rectangular column — the soft corners come from the outer
|
// Plain rectangular column — the soft corners come from the outer
|
||||||
// rounded scroll viewport, so inner rounding would look odd at the edges.
|
// rounded scroll viewport, so inner rounding would look odd at the edges.
|
||||||
@@ -597,8 +605,10 @@ private fun DayColumnCard(
|
|||||||
// only fires on the column background. Snaps to the tapped hour.
|
// only fires on the column background. Snaps to the tapped hour.
|
||||||
.pointerInput(date) {
|
.pointerInput(date) {
|
||||||
detectTapGestures { offset ->
|
detectTapGestures { offset ->
|
||||||
val hour = (offset.y / hourPx).toInt().coerceIn(0, 23)
|
currentOnCreateAt.value(
|
||||||
onCreateAt(date, hour * 60)
|
date,
|
||||||
|
tappedMinuteOfDay(offset.y, currentHourPx.value),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -107,6 +108,7 @@ import de.jeanlucmakiola.calendula.ui.common.rememberTimelinePinchZoom
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.formatHourLabel
|
import de.jeanlucmakiola.calendula.ui.common.formatHourLabel
|
||||||
import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
||||||
import de.jeanlucmakiola.calendula.ui.common.hourSeparatorLines
|
import de.jeanlucmakiola.calendula.ui.common.hourSeparatorLines
|
||||||
|
import de.jeanlucmakiola.calendula.ui.common.tappedMinuteOfDay
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
||||||
import de.jeanlucmakiola.calendula.ui.common.next
|
import de.jeanlucmakiola.calendula.ui.common.next
|
||||||
import de.jeanlucmakiola.floret.time.isoWeekNumber
|
import de.jeanlucmakiola.floret.time.isoWeekNumber
|
||||||
@@ -722,6 +724,12 @@ private fun DayColumnCard(
|
|||||||
val hourPx = with(LocalDensity.current) { hourHeight.toPx() }
|
val hourPx = with(LocalDensity.current) { hourHeight.toPx() }
|
||||||
val showHourLines = LocalShowHourLines.current
|
val showHourLines = LocalShowHourLines.current
|
||||||
val hourLineColor = MaterialTheme.colorScheme.outlineVariant
|
val hourLineColor = MaterialTheme.colorScheme.outlineVariant
|
||||||
|
// The tap detector outlives the composition that installed it — a pinch or a
|
||||||
|
// Settings change moves the hour height without restarting it — so it reads
|
||||||
|
// the height and the callback through state handles instead of capturing
|
||||||
|
// them, or taps land on the scale the column had before the zoom (#148).
|
||||||
|
val currentHourPx = rememberUpdatedState(hourPx)
|
||||||
|
val currentOnCreateAt = rememberUpdatedState(onCreateAt)
|
||||||
Card(
|
Card(
|
||||||
// Plain rectangular columns — the soft corners come from the outer
|
// Plain rectangular columns — the soft corners come from the outer
|
||||||
// rounded scroll viewport, so inner rounding would look odd at the edges.
|
// rounded scroll viewport, so inner rounding would look odd at the edges.
|
||||||
@@ -741,8 +749,10 @@ private fun DayColumnCard(
|
|||||||
// blocks are consumed by their own handler first. Snaps to hour.
|
// blocks are consumed by their own handler first. Snaps to hour.
|
||||||
.pointerInput(date) {
|
.pointerInput(date) {
|
||||||
detectTapGestures { offset ->
|
detectTapGestures { offset ->
|
||||||
val hour = (offset.y / hourPx).toInt().coerceIn(0, 23)
|
currentOnCreateAt.value(
|
||||||
onCreateAt(date, hour * 60)
|
date,
|
||||||
|
tappedMinuteOfDay(offset.y, currentHourPx.value),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -491,4 +491,5 @@
|
|||||||
<item quantity="many">Importando %d eventos</item>
|
<item quantity="many">Importando %d eventos</item>
|
||||||
<item quantity="other">Importando %d eventos</item>
|
<item quantity="other">Importando %d eventos</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="settings_soften_colors">Armoniza los colores del calendario</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -126,4 +126,24 @@ class TimelineScaleTest {
|
|||||||
assertThat(parseTimelineScale(stored)).isEqualTo(TimelineScale.Regular)
|
assertThat(parseTimelineScale(stored)).isEqualTo(TimelineScale.Regular)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a tap creates at the hour it landed in, at whatever scale`() {
|
||||||
|
// Same point on the same column reads as a different hour once the
|
||||||
|
// timeline has been pinched — which is the whole of #148: the tap has to
|
||||||
|
// be measured against the height the column is drawing at now.
|
||||||
|
assertThat(tappedMinuteOfDay(offsetY = 500f, hourPx = 100f)).isEqualTo(5 * 60)
|
||||||
|
assertThat(tappedMinuteOfDay(offsetY = 500f, hourPx = 50f)).isEqualTo(10 * 60)
|
||||||
|
// Within an hour it snaps back to that hour's start.
|
||||||
|
assertThat(tappedMinuteOfDay(offsetY = 599f, hourPx = 100f)).isEqualTo(5 * 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a tap outside the day stays inside it`() {
|
||||||
|
assertThat(tappedMinuteOfDay(offsetY = -20f, hourPx = 56f)).isEqualTo(0)
|
||||||
|
assertThat(tappedMinuteOfDay(offsetY = 99_999f, hourPx = 56f)).isEqualTo(23 * 60)
|
||||||
|
// A viewport measured at nothing has no grid to read — midnight, not a
|
||||||
|
// division by zero.
|
||||||
|
assertThat(tappedMinuteOfDay(offsetY = 500f, hourPx = 0f)).isEqualTo(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
fastlane/metadata/android/ar/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/ar/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
إصلاحات
|
||||||
|
• عرض الأسبوع واليوم: النقر على مساحة فارغة يُنشئ الحدث في الساعة التي نقرت عليها، حتى بعد تكبير شريط الوقت أو تغيير ارتفاع الساعة.
|
||||||
2
fastlane/metadata/android/de-DE/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Behoben
|
||||||
|
• Wochen- und Tagesansicht: Ein Tippen auf einen freien Bereich legt den Termin jetzt zur angetippten Uhrzeit an – auch nachdem die Zeitleiste gezoomt oder die Stundenhöhe geändert wurde.
|
||||||
2
fastlane/metadata/android/en-GB/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/en-GB/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Fixed
|
||||||
|
• Week and day view: tapping an empty slot now creates the event at the hour you tapped, even after zooming the timeline or changing the hour height.
|
||||||
2
fastlane/metadata/android/en-US/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Fixed
|
||||||
|
• Week and day view: tapping an empty slot now creates the event at the hour you tapped, even after zooming the timeline or changing the hour height.
|
||||||
2
fastlane/metadata/android/es-ES/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/es-ES/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Corregido
|
||||||
|
• Vista de semana y día: al tocar un hueco libre, el evento se crea a la hora que has tocado, incluso después de ampliar la línea de tiempo o cambiar la altura de la hora.
|
||||||
2
fastlane/metadata/android/fr-FR/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Corrigé
|
||||||
|
• Vues Semaine et Jour : appuyer sur un créneau vide crée désormais l'événement à l'heure touchée, même après avoir zoomé sur la grille ou modifié la hauteur d'une heure.
|
||||||
2
fastlane/metadata/android/it-IT/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Corretto
|
||||||
|
• Vista settimana e giorno: toccando uno spazio libero l'evento viene creato all'ora toccata, anche dopo aver ingrandito la linea temporale o cambiato l'altezza dell'ora.
|
||||||
2
fastlane/metadata/android/pl-PL/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/pl-PL/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Poprawki
|
||||||
|
• Widok tygodnia i dnia: dotknięcie pustego miejsca tworzy teraz wydarzenie o dotkniętej godzinie, także po powiększeniu osi czasu lub zmianie wysokości godziny.
|
||||||
2
fastlane/metadata/android/pt-PT/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/pt-PT/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Corrigido
|
||||||
|
• Vista de semana e de dia: tocar num espaço livre cria o evento à hora tocada, mesmo depois de ampliar a linha temporal ou alterar a altura da hora.
|
||||||
2
fastlane/metadata/android/ru-RU/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/ru-RU/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Исправлено
|
||||||
|
• Вид недели и дня: нажатие на свободное место создаёт событие на выбранный час — в том числе после масштабирования шкалы времени или изменения высоты часа.
|
||||||
2
fastlane/metadata/android/zh-CN/changelogs/21801.txt
Normal file
2
fastlane/metadata/android/zh-CN/changelogs/21801.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
修复
|
||||||
|
• 周视图和日视图:点按空白处会在所点的时间创建事件,即使在缩放时间轴或更改小时高度之后也是如此。
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "9.2.1"
|
agp = "9.2.1"
|
||||||
kotlin = "2.3.21"
|
kotlin = "2.4.10"
|
||||||
ksp = "2.3.9"
|
ksp = "2.3.9"
|
||||||
hilt = "2.59.2"
|
hilt = "2.59.2"
|
||||||
coreKtx = "1.19.0"
|
coreKtx = "1.19.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user