Compare commits
1 Commits
fix/214-wi
...
d03985eada
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d03985eada |
41
CHANGELOG.md
41
CHANGELOG.md
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.19.2] — 2026-08-17
|
||||
|
||||
### Changed
|
||||
- **A meeting you declined is now struck through** wherever it appears — month,
|
||||
week, day, agenda, search and both widgets — and no longer schedules a
|
||||
reminder. Declining an invitation in Google Calendar left the event looking
|
||||
like any other in Calendula, and it still notified you about a meeting you had
|
||||
said no to. It stays visible rather than disappearing: the organiser still
|
||||
expects an answer from you, and the slot is still spoken for ([#180]).
|
||||
|
||||
### Fixed
|
||||
- **Tapping an event in month view opens the event**, not the day it sits on.
|
||||
Every month style is affected — page, rolling, seamless weeks — and until now
|
||||
the only way to reach an event from the month was to open its day first and
|
||||
find it again there. Tapping anywhere else in the cell still opens the day
|
||||
([#187]).
|
||||
- **An edit made to an event now shows the moment you re-open it.** Adding a
|
||||
description to an event and opening it again showed the sheet as it was before
|
||||
the save, because the detail was only re-read when a different occurrence was
|
||||
opened ([#196]).
|
||||
- **The month widget's arrows stopped working after a couple of taps.** The grid
|
||||
was serialised as roughly 740 views, and one update ran to half a megabyte —
|
||||
more than the launcher's buffer takes. The third update overran it, and Android
|
||||
responded by dropping the whole widget host, which killed updates for *every*
|
||||
widget on the home screen, ours and other apps', until the launcher rebound.
|
||||
The grid now draws 192 views, and a resized widget reflows to its new size
|
||||
instead of clipping ([#214]).
|
||||
- Tapping a widget's refresh or month arrows redraws that widget by its own id
|
||||
instead of asking Android to update all of them, which does nothing in a
|
||||
process the tap has just woken from cold ([#18]).
|
||||
- **Jump-to-today in seamless weeks lands on the current week** instead of
|
||||
leaving a sliver of the previous row on screen ([#191]).
|
||||
- Day and week view no longer run their events flush against the right edge
|
||||
([#192]).
|
||||
|
||||
## [2.19.1] — 2026-08-11
|
||||
|
||||
### Added
|
||||
@@ -1414,3 +1449,9 @@ automatically, with zero telemetry and no internet permission.
|
||||
[#123]: https://codeberg.org/jlmakiola/calendula/issues/123
|
||||
[#163]: https://codeberg.org/jlmakiola/calendula/issues/163
|
||||
[#173]: https://codeberg.org/jlmakiola/calendula/issues/173
|
||||
[#180]: https://codeberg.org/jlmakiola/calendula/issues/180
|
||||
[#187]: https://codeberg.org/jlmakiola/calendula/issues/187
|
||||
[#191]: https://codeberg.org/jlmakiola/calendula/issues/191
|
||||
[#192]: https://codeberg.org/jlmakiola/calendula/issues/192
|
||||
[#196]: https://codeberg.org/jlmakiola/calendula/issues/196
|
||||
[#214]: https://codeberg.org/jlmakiola/calendula/issues/214
|
||||
|
||||
@@ -48,9 +48,14 @@ class EventDetailViewModel @Inject constructor(
|
||||
) : ViewModel() {
|
||||
|
||||
private val _target = MutableStateFlow<Target?>(null)
|
||||
// Bumped by retry() to re-run the load for the same target.
|
||||
// Bumped by retry() and by re-opening the target already shown, to re-run
|
||||
// the load without changing the target.
|
||||
private val _reload = MutableStateFlow(0)
|
||||
|
||||
// Last target whose content is already on screen; a re-read of it skips the
|
||||
// Loading skeleton so the sheet doesn't blank out between two identical reads.
|
||||
private var loadedTarget: Target? = null
|
||||
|
||||
private val _deleteState = MutableStateFlow<DeleteUiState>(DeleteUiState.Idle)
|
||||
val deleteState: StateFlow<DeleteUiState> = _deleteState.asStateFlow()
|
||||
|
||||
@@ -58,11 +63,14 @@ class EventDetailViewModel @Inject constructor(
|
||||
combine(_target, _reload) { target, _ -> target }
|
||||
.flatMapLatest { target ->
|
||||
if (target == null) {
|
||||
loadedTarget = null
|
||||
flowOf<EventDetailUiState>(EventDetailUiState.Loading)
|
||||
} else {
|
||||
flow {
|
||||
emit(EventDetailUiState.Loading)
|
||||
emit(loadDetail(target))
|
||||
if (loadedTarget != target) emit(EventDetailUiState.Loading)
|
||||
val loaded = loadDetail(target)
|
||||
loadedTarget = target.takeIf { loaded is EventDetailUiState.Success }
|
||||
emit(loaded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2177,7 +2177,7 @@ private fun MonthWeekRow(
|
||||
// leaves both the click and a pickup in flight untouched. Padded and
|
||||
// clipped to the background pill so the ripple matches it. A blanked
|
||||
// cell isn't part of this month, so it takes no taps either.
|
||||
val downY = remember(week.days.size) { FloatArray(week.days.size) }
|
||||
val downY = remember(week.days.size) { FloatArray(week.days.size) { NO_DOWN_Y } }
|
||||
Row(Modifier.matchParentSize()) {
|
||||
week.days.forEachIndexed { col, d ->
|
||||
if (blankOutside && !inMonth(d)) {
|
||||
@@ -2198,9 +2198,14 @@ private fun MonthWeekRow(
|
||||
.padding(horizontal = CELL_GAP, vertical = 1.dp)
|
||||
.clip(CELL_SHAPE)
|
||||
.clickable {
|
||||
// Cleared on read: a click with no fresh down
|
||||
// (TalkBack, D-pad) would otherwise resolve the
|
||||
// previous tap's position and reopen its chip.
|
||||
val cellY = downY[col]
|
||||
downY[col] = NO_DOWN_Y
|
||||
val chip = week.chipAtCellY(
|
||||
col = col,
|
||||
cellY = downY[col],
|
||||
cellY = cellY,
|
||||
bandTopInCell = bandTopInCell(
|
||||
cellCoordinates,
|
||||
bandCoordinates,
|
||||
@@ -2231,6 +2236,11 @@ private fun bandTopInCell(
|
||||
return bandTop - cellTop
|
||||
}
|
||||
|
||||
/**
|
||||
* Stand-in [cellY] for "no touch down recorded", which resolves to no chip.
|
||||
*/
|
||||
private const val NO_DOWN_Y = Float.NEGATIVE_INFINITY
|
||||
|
||||
/**
|
||||
* The chip at [cellY] in column [col], where [cellY] is measured from the top of
|
||||
* the row's day-column box. Null for a tap above the band (the day number), on an
|
||||
|
||||
@@ -74,11 +74,6 @@ import kotlinx.datetime.toLocalDateTime
|
||||
import kotlin.time.Instant
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* "Upcoming" agenda widget — a continuously scrolling list of the next ~30 days
|
||||
* of events grouped under day headers (the Google "Schedule" widget model).
|
||||
* Reuses the app's [groupAgendaDays] grouping so it matches the in-app agenda.
|
||||
*/
|
||||
/**
|
||||
* Per-instance Glance state key holding the agenda range (as [AgendaRange.storageValue]).
|
||||
* The range is read reactively in the composition ([currentState]) so a settings
|
||||
@@ -113,6 +108,11 @@ internal val AGENDA_SHOW_TODAY_STATE_KEY = booleanPreferencesKey("agenda_show_to
|
||||
*/
|
||||
internal val AGENDA_SIZE_KEY = stringPreferencesKey("widget_size")
|
||||
|
||||
/**
|
||||
* "Upcoming" agenda widget — a continuously scrolling list of the next ~30 days
|
||||
* of events grouped under day headers (the Google "Schedule" widget model).
|
||||
* Reuses the app's [groupAgendaDays] grouping so it matches the in-app agenda.
|
||||
*/
|
||||
class AgendaWidget : GlanceAppWidget() {
|
||||
|
||||
override val stateDefinition = PreferencesGlanceStateDefinition
|
||||
@@ -136,9 +136,11 @@ class AgendaWidget : GlanceAppWidget() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-reads the calendar and redraws the widget (header refresh button). Targets
|
||||
* the tapped widget's own id rather than `updateAll`, whose provider-name lookup
|
||||
* is empty in a process a tap woke from cold — see `ShiftMonthAction` (#18).
|
||||
* Redraws the widget (header refresh button). Targets the tapped widget's own id
|
||||
* rather than `updateAll`, whose provider-name lookup is empty in a process a tap
|
||||
* woke from cold — see `ShiftMonthAction` (#18). A cold process re-reads the
|
||||
* calendar in the `provideGlance` preamble; a live session only recomposes from
|
||||
* the snapshot it already has (see [AGENDA_RANGE_KEY]).
|
||||
*/
|
||||
class RefreshAgendaAction : ActionCallback {
|
||||
override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
|
||||
@@ -149,7 +151,7 @@ class RefreshAgendaAction : ActionCallback {
|
||||
/**
|
||||
* Upper bound on rows handed to the [LazyColumn], so the serialized RemoteViews
|
||||
* stays well inside the binder transaction limit regardless of range and calendar
|
||||
* size (see the [SizeMode.Exact] note above). Far more than fits on screen — a
|
||||
* size (see the [SizeMode.Single] note above). Far more than fits on screen — a
|
||||
* user scrolling a home-screen widget past a hundred rows is not a case worth
|
||||
* risking a failed update for.
|
||||
*/
|
||||
|
||||
@@ -32,6 +32,8 @@ import kotlin.time.Instant
|
||||
/**
|
||||
* Re-opening an occurrence must re-read it (#196): the view model outlives the
|
||||
* sheet, so an edit that changed no time would otherwise show the pre-save row.
|
||||
* The re-read stays silent — the loaded content must not blink back to the
|
||||
* skeleton on the way.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class EventDetailViewModelTest {
|
||||
@@ -72,19 +74,20 @@ class EventDetailViewModelTest {
|
||||
return EventDetailViewModel(repo, IcsExporter(ContextWrapper(null)), dispatcher)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `re-opening the same occurrence re-reads it`(@TempDir tempDir: Path) = runTest(dispatcher) {
|
||||
var stored: String? = null
|
||||
val fake = FakeCalendarDataSource().apply {
|
||||
private fun fakeSource(description: () -> String?) = FakeCalendarDataSource().apply {
|
||||
calendarsResult = listOf(
|
||||
CalendarSource(
|
||||
id = 1L, displayName = "Cal", accountName = "acc@local", accountType = "LOCAL",
|
||||
color = 0xFF112233.toInt(), isVisibleInSystem = true, canModifyContents = true,
|
||||
),
|
||||
)
|
||||
eventDetailResult = { detail(stored) }
|
||||
eventDetailResult = { detail(description()) }
|
||||
}
|
||||
val vm = viewModel(tempDir, fake)
|
||||
|
||||
@Test
|
||||
fun `re-opening the same occurrence re-reads it`(@TempDir tempDir: Path) = runTest(dispatcher) {
|
||||
var stored: String? = null
|
||||
val vm = viewModel(tempDir, fakeSource { stored })
|
||||
val collector = launch(Job()) { vm.state.collect {} }
|
||||
|
||||
vm.open(42L, beginMillis, endMillis)
|
||||
@@ -100,4 +103,22 @@ class EventDetailViewModelTest {
|
||||
|
||||
collector.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the re-read does not fall back to the skeleton`(@TempDir tempDir: Path) = runTest(dispatcher) {
|
||||
val vm = viewModel(tempDir, fakeSource { null })
|
||||
val seen = mutableListOf<EventDetailUiState>()
|
||||
val collector = launch(Job()) { vm.state.collect { seen += it } }
|
||||
|
||||
vm.open(42L, beginMillis, endMillis)
|
||||
advanceUntilIdle()
|
||||
assertThat(vm.state.value).isInstanceOf(EventDetailUiState.Success::class.java)
|
||||
|
||||
seen.clear()
|
||||
vm.open(42L, beginMillis, endMillis)
|
||||
advanceUntilIdle()
|
||||
assertThat(seen).doesNotContain(EventDetailUiState.Loading)
|
||||
|
||||
collector.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
8
fastlane/metadata/android/ar/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/ar/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
إصلاحات
|
||||
• النقر على حدث في عرض الشهر يفتح الحدث نفسه بدل يومه.
|
||||
• التعديل يظهر فور إعادة فتح الحدث.
|
||||
• لم تعد أسهم أداة الشهر تتوقف بعد بضع نقرات: تحديث ضخم كان يُسقط مضيف الأدوات في المشغّل.
|
||||
• «اليوم» في الأسابيع المتصلة يصل إلى الأسبوع الحالي تماماً، وعادت مسافة الهامش على يسار عرضي اليوم والأسبوع.
|
||||
|
||||
تغييرات
|
||||
• الحدث الذي رفضته يظهر مشطوباً في كل مكان ولم يعد يُذكّرك به.
|
||||
8
fastlane/metadata/android/de-DE/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/de-DE/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Behoben
|
||||
• Ein Tippen auf einen Termin in der Monatsansicht öffnet ihn statt des Tages.
|
||||
• Eine Änderung ist sofort sichtbar, wenn der Termin neu geöffnet wird.
|
||||
• Die Pfeile des Monats-Widgets sterben nicht mehr nach wenigen Tipps – eine zu große Aktualisierung riss den Widget-Host mit.
|
||||
• „Heute“ springt in nahtlosen Wochen genau auf die aktuelle Woche; Wochen- und Tagesansicht haben rechts wieder Abstand.
|
||||
|
||||
Geändert
|
||||
• Ein abgelehnter Termin ist überall durchgestrichen und erinnert nicht mehr.
|
||||
8
fastlane/metadata/android/en-GB/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/en-GB/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Fixed
|
||||
• Tapping an event in month view now opens the event instead of its day.
|
||||
• An edit shows straight away when you re-open the event.
|
||||
• The month widget's arrows no longer die after a few taps — an oversized update was killing the launcher's widget host.
|
||||
• Jump-to-today in seamless weeks lands on the current week, and day/week view no longer run flush against the right edge.
|
||||
|
||||
Changed
|
||||
• A meeting you declined is struck through everywhere and no longer reminds you.
|
||||
8
fastlane/metadata/android/en-US/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/en-US/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Fixed
|
||||
• Tapping an event in month view now opens the event instead of its day.
|
||||
• An edit shows straight away when you re-open the event.
|
||||
• The month widget's arrows no longer die after a few taps — an oversized update was killing the launcher's widget host.
|
||||
• Jump-to-today in seamless weeks lands on the current week, and day/week view no longer run flush against the right edge.
|
||||
|
||||
Changed
|
||||
• A meeting you declined is struck through everywhere and no longer reminds you.
|
||||
8
fastlane/metadata/android/es-ES/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/es-ES/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Corregido
|
||||
• Tocar un evento en la vista de mes abre el evento, no su día.
|
||||
• Un cambio se ve en cuanto vuelves a abrir el evento.
|
||||
• Las flechas del widget de mes ya no se quedan muertas tras unos toques: una actualización demasiado grande tumbaba el host de widgets del launcher.
|
||||
• «Hoy» en semanas continuas llega justo a la semana actual, y las vistas de día y semana vuelven a tener margen a la derecha.
|
||||
|
||||
Cambios
|
||||
• Un evento que has rechazado aparece tachado en todas partes y ya no avisa.
|
||||
8
fastlane/metadata/android/fr-FR/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/fr-FR/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Corrigé
|
||||
• Appuyer sur un événement en vue mois ouvre l'événement, pas sa journée.
|
||||
• Une modification apparaît dès que l'événement est rouvert.
|
||||
• Les flèches du widget mois ne meurent plus après quelques appuis : une mise à jour trop lourde emportait l'hôte de widgets du lanceur.
|
||||
• « Aujourd'hui » en semaines continues arrive pile sur la semaine en cours ; les vues jour et semaine ont de nouveau une marge à droite.
|
||||
|
||||
Modifié
|
||||
• Un événement refusé est barré partout et ne vous rappelle plus rien.
|
||||
8
fastlane/metadata/android/it-IT/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/it-IT/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Corretto
|
||||
• Toccare un evento nella vista mese apre l'evento, non il suo giorno.
|
||||
• Una modifica si vede subito riaprendo l'evento.
|
||||
• Le frecce del widget mese non muoiono più dopo pochi tocchi: un aggiornamento troppo grande abbatteva l'host dei widget del launcher.
|
||||
• «Oggi» nelle settimane continue arriva esattamente sulla settimana corrente, e le viste giorno e settimana hanno di nuovo un margine a destra.
|
||||
|
||||
Modifiche
|
||||
• Un evento rifiutato è barrato ovunque e non invia più promemoria.
|
||||
8
fastlane/metadata/android/pl-PL/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/pl-PL/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Poprawki
|
||||
• Dotknięcie wydarzenia w widoku miesiąca otwiera wydarzenie, a nie jego dzień.
|
||||
• Zmiana jest widoczna od razu po ponownym otwarciu wydarzenia.
|
||||
• Strzałki widżetu miesiąca nie zamierają już po kilku dotknięciach – zbyt duża aktualizacja kładła host widżetów launchera.
|
||||
• „Dziś” w ciągłych tygodniach trafia dokładnie w bieżący tydzień, a widoki dnia i tygodnia znów mają margines z prawej.
|
||||
|
||||
Zmiany
|
||||
• Odrzucone wydarzenie jest wszędzie przekreślone i nie przypomina o sobie.
|
||||
8
fastlane/metadata/android/pt-BR/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/pt-BR/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Corrigido
|
||||
• Tocar em um evento na visualização de mês abre o evento, e não o seu dia.
|
||||
• Uma alteração aparece assim que o evento é reaberto.
|
||||
• As setas do widget de mês não morrem mais depois de alguns toques: uma atualização grande demais derrubava o host de widgets do launcher.
|
||||
• "Hoje" nas semanas contínuas chega exatamente na semana atual, e as visualizações de dia e semana voltaram a ter margem à direita.
|
||||
|
||||
Alterações
|
||||
• Um evento recusado fica riscado em todo lugar e não lembra mais você.
|
||||
8
fastlane/metadata/android/pt-PT/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/pt-PT/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Corrigido
|
||||
• Tocar num evento na vista de mês abre o evento e não o seu dia.
|
||||
• Uma alteração aparece assim que o evento é reaberto.
|
||||
• As setas do widget de mês já não morrem ao fim de alguns toques: uma atualização demasiado grande derrubava o anfitrião de widgets do launcher.
|
||||
• «Hoje» nas semanas contínuas chega mesmo à semana atual, e as vistas de dia e semana voltam a ter margem à direita.
|
||||
|
||||
Alterações
|
||||
• Um evento recusado fica riscado em todo o lado e deixa de lembrar.
|
||||
8
fastlane/metadata/android/ru-RU/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/ru-RU/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Исправлено
|
||||
• Нажатие на событие в виде месяца открывает событие, а не его день.
|
||||
• Изменение видно сразу при повторном открытии события.
|
||||
• Стрелки виджета месяца больше не отмирают после нескольких нажатий: слишком большое обновление роняло хост виджетов лаунчера.
|
||||
• «Сегодня» в непрерывных неделях попадает точно на текущую неделю, а у видов дня и недели снова есть отступ справа.
|
||||
|
||||
Изменения
|
||||
• Отклонённое событие везде зачёркнуто и больше не напоминает о себе.
|
||||
8
fastlane/metadata/android/zh-CN/changelogs/21902.txt
Normal file
8
fastlane/metadata/android/zh-CN/changelogs/21902.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
修复
|
||||
• 在月视图中点按事件现在会打开该事件本身,而不是它所在的那一天。
|
||||
• 编辑后再次打开事件,改动会立即显示。
|
||||
• 月视图小部件的箭头不再点几下就失灵:过大的更新会拖垮启动器的小部件宿主。
|
||||
• 连续周视图中的“今天”会精确定位到本周,日视图和周视图右侧也重新留出了间距。
|
||||
|
||||
变更
|
||||
• 已拒绝的事件在各处均以删除线标记,并且不再提醒。
|
||||
Reference in New Issue
Block a user