release: v2.9.0 #37
16
CHANGELOG.md
16
CHANGELOG.md
@@ -12,14 +12,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
Appearance) lets you pick Month, Week, Day or Agenda as the view shown each
|
||||
time you start the app, instead of always opening on Week. Thanks to
|
||||
@devinside for the suggestion ([#1]).
|
||||
- The month widget is now interactive: tap any day to open it, or tap an event to
|
||||
open its details. Previously only the month's prev/next/today controls
|
||||
responded. Thanks to @devinside for spotting this ([#2]).
|
||||
|
||||
### Fixed
|
||||
- Home-screen widgets now back out the way you came in. Opening a day or event
|
||||
from the Agenda widget keeps you in the agenda context, and the Month widget
|
||||
keeps you in the month context — pressing Back returns to that view instead of
|
||||
dropping you on the week view. The app now keeps a proper view history: Back
|
||||
steps from a drilled-in day to the view you opened it from, then to your
|
||||
default view, then leaves the app. Thanks to @devinside for reporting ([#2]).
|
||||
- Home-screen widgets now back out the way you came in. A day or event opened
|
||||
from the Agenda widget keeps you in the agenda context, and from the Month
|
||||
widget in the month context — pressing Back returns there instead of dropping
|
||||
you on the week view. More broadly, the app now keeps a real view history:
|
||||
switching views and drilling into a day are retraced by Back one step at a
|
||||
time, down to your default view before the app exits. Thanks to @devinside for
|
||||
reporting ([#2]).
|
||||
|
||||
## [2.8.0] — 2026-06-23
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import de.jeanlucmakiola.calendula.ui.calendars.CalendarsScreen
|
||||
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
||||
import de.jeanlucmakiola.calendula.ui.common.drillToDay
|
||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
||||
import de.jeanlucmakiola.calendula.ui.common.selectView
|
||||
import de.jeanlucmakiola.calendula.ui.common.viewBaseStack
|
||||
import de.jeanlucmakiola.calendula.ui.day.DayScreen
|
||||
import de.jeanlucmakiola.calendula.ui.detail.EventDetailScreen
|
||||
@@ -46,11 +47,12 @@ import kotlin.time.Clock
|
||||
* pill in their top bars writes back here via [onSelectView].
|
||||
*
|
||||
* The stack's bottom is the user's [CalendarHostViewModel.defaultView] home view.
|
||||
* A lateral move (pill / drawer / a widget establishing its own view) replaces
|
||||
* the non-home top; a date tap drills the day view on top. Pressing back pops
|
||||
* one level, and the base-level [BackHandler] hands off to the system to exit
|
||||
* once only the home view remains. So back from a widget-opened screen returns
|
||||
* to that widget's view, then home, then out.
|
||||
* A lateral switch (pill / drawer) builds a visit history so back retraces it
|
||||
* (see [selectView]); a widget launch resets the stack to its own view; a date
|
||||
* tap drills the day view on top. Pressing back pops one level, and the
|
||||
* base-level [BackHandler] hands off to the system to exit once only the home
|
||||
* view remains. So back from a widget-opened screen returns to that widget's
|
||||
* view, then home; and back through pill switches walks the views in reverse.
|
||||
*
|
||||
* [requestedDetailKey] is an externally requested occurrence (a tapped
|
||||
* reminder notification routed through MainActivity): it opens the detail
|
||||
@@ -79,7 +81,7 @@ fun CalendarHost(
|
||||
mutableStateOf(listOf(defaultView))
|
||||
}
|
||||
val view = viewStack.last()
|
||||
val onSelectView: (CalendarView) -> Unit = { viewStack = viewBaseStack(defaultView, it) }
|
||||
val onSelectView: (CalendarView) -> Unit = { viewStack = viewStack.selectView(it) }
|
||||
|
||||
// Tapping a day in the month grid opens the day view anchored to that date.
|
||||
var pendingDayIso by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
|
||||
@@ -52,19 +52,27 @@ fun CalendarView.next(available: List<CalendarView> = IMPLEMENTED_VIEWS): Calend
|
||||
|
||||
/**
|
||||
* The top-level view back stack (bottom → top): the [default] home view always
|
||||
* sits at the bottom, with a laterally-selected or widget-entered view layered
|
||||
* above it. Pressing back pops one level until only the home view remains, then
|
||||
* the system exits the app.
|
||||
* sits at the bottom. Pressing back pops one level until only the home view
|
||||
* remains, then the system exits the app. Three operations move it:
|
||||
*
|
||||
* [viewBaseStack] is the stack after a lateral move to [top] (the switcher pill,
|
||||
* the drawer, or a widget establishing its own view): just `[default]` when
|
||||
* [top] is the home view, else `[default, top]`. It collapses any prior drill-in
|
||||
* so the depth never grows past a single lateral level. [drillToDay] pushes the
|
||||
* day view on top — a date tap from the month grid or a widget — unless the day
|
||||
* view is already current.
|
||||
* - [selectView] — a lateral switch (the pill or the drawer). It keeps a visit
|
||||
* history so back retraces it: switching to a not-yet-visited view pushes it on
|
||||
* top; switching to one already in the stack pops back to it (collapsing the
|
||||
* loop, so the depth stays bounded by the number of distinct views). Selecting
|
||||
* the home view collapses to just `[default]`.
|
||||
* - [viewBaseStack] — the stack a widget launch resets to: just `[default]` when
|
||||
* the widget's view is the home view, else `[default, source]`. A widget entry
|
||||
* starts a fresh context rather than extending in-app history.
|
||||
* - [drillToDay] — pushes the day view on top (a date tap from the month grid or
|
||||
* a widget), unless the day view is already current.
|
||||
*/
|
||||
fun viewBaseStack(default: CalendarView, top: CalendarView): List<CalendarView> =
|
||||
if (top == default) listOf(default) else listOf(default, top)
|
||||
fun List<CalendarView>.selectView(target: CalendarView): List<CalendarView> {
|
||||
val existing = indexOf(target)
|
||||
return if (existing >= 0) take(existing + 1) else this + target
|
||||
}
|
||||
|
||||
fun viewBaseStack(default: CalendarView, source: CalendarView): List<CalendarView> =
|
||||
if (source == default) listOf(default) else listOf(default, source)
|
||||
|
||||
/** Push the day view as a drill-in over the current stack (no-op if already on it). */
|
||||
fun List<CalendarView>.drillToDay(): List<CalendarView> =
|
||||
|
||||
@@ -303,8 +303,16 @@ private fun WeekRow(
|
||||
|
||||
@Composable
|
||||
private fun DayNumber(date: LocalDate, isToday: Boolean, inMonth: Boolean, colW: Dp) {
|
||||
val context = LocalContext.current
|
||||
Box(
|
||||
modifier = GlanceModifier.width(colW).height(DAY_NUMBER_HEIGHT),
|
||||
modifier = GlanceModifier
|
||||
.width(colW)
|
||||
.height(DAY_NUMBER_HEIGHT)
|
||||
// Tap a day number to open that day, rooted in the month view so back
|
||||
// returns to the month grid.
|
||||
.clickable(
|
||||
actionStartActivity(MainActivity.openDateIntent(context, date, CalendarView.Month)),
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Box(
|
||||
@@ -356,7 +364,24 @@ private fun LaneRow(week: MonthWeek, lane: Int, dark: Boolean, colW: Dp) {
|
||||
@Composable
|
||||
private fun SpanBar(event: EventInstance, dark: Boolean, width: Dp) {
|
||||
val context = LocalContext.current
|
||||
Box(modifier = GlanceModifier.width(width).height(LANE_HEIGHT).padding(horizontal = 1.dp)) {
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.width(width)
|
||||
.height(LANE_HEIGHT)
|
||||
.padding(horizontal = 1.dp)
|
||||
// Tap an event bar to open its detail, rooted in the month view.
|
||||
.clickable(
|
||||
actionStartActivity(
|
||||
MainActivity.openEventIntent(
|
||||
context = context,
|
||||
eventId = event.eventId,
|
||||
beginMillis = event.start.toEpochMilliseconds(),
|
||||
endMillis = event.end.toEpochMilliseconds(),
|
||||
source = CalendarView.Month,
|
||||
),
|
||||
),
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxSize()
|
||||
|
||||
@@ -12,25 +12,44 @@ import org.junit.jupiter.api.Test
|
||||
class ViewBackStackTest {
|
||||
|
||||
@Test
|
||||
fun `lateral move to the home view collapses to just the home`() {
|
||||
fun `a widget whose view is the home view resets to just the home`() {
|
||||
assertThat(viewBaseStack(CalendarView.Week, CalendarView.Week))
|
||||
.containsExactly(CalendarView.Week)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `lateral move to a non-home view layers it over the home`() {
|
||||
fun `a widget launch resets to its view over the home`() {
|
||||
assertThat(viewBaseStack(CalendarView.Week, CalendarView.Month))
|
||||
.containsExactly(CalendarView.Week, CalendarView.Month)
|
||||
.inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a lateral move never grows past one level above home`() {
|
||||
// Switching pill targets in turn replaces the top rather than stacking.
|
||||
val afterMonth = viewBaseStack(CalendarView.Week, CalendarView.Month)
|
||||
val afterAgenda = viewBaseStack(CalendarView.Week, CalendarView.Agenda)
|
||||
assertThat(afterMonth).hasSize(2)
|
||||
assertThat(afterAgenda).containsExactly(CalendarView.Week, CalendarView.Agenda).inOrder()
|
||||
fun `pill switches build a visit history that back retraces`() {
|
||||
// Agenda → Day → Week → Month, each a new view, stacks the full history.
|
||||
val stack = listOf(CalendarView.Agenda)
|
||||
.selectView(CalendarView.Day)
|
||||
.selectView(CalendarView.Week)
|
||||
.selectView(CalendarView.Month)
|
||||
assertThat(stack).containsExactly(
|
||||
CalendarView.Agenda, CalendarView.Day, CalendarView.Week, CalendarView.Month,
|
||||
).inOrder()
|
||||
// Back walks the views in reverse, not straight to the home view.
|
||||
assertThat(stack.dropLast(1).last()).isEqualTo(CalendarView.Week)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selecting an already-visited view collapses the loop back to it`() {
|
||||
val stack = listOf(CalendarView.Week, CalendarView.Agenda, CalendarView.Day)
|
||||
.selectView(CalendarView.Agenda)
|
||||
assertThat(stack).containsExactly(CalendarView.Week, CalendarView.Agenda).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `selecting the home view collapses to just the home`() {
|
||||
val stack = listOf(CalendarView.Week, CalendarView.Agenda, CalendarView.Day)
|
||||
.selectView(CalendarView.Week)
|
||||
assertThat(stack).containsExactly(CalendarView.Week)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user