Merge remote-tracking branch 'origin/release/2.24.0' into prep/2.24.0
# Conflicts: # CHANGELOG.md
This commit is contained in:
commit
5dd522e4c6
3 files changed
+46
-9
No files matched your search
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- **Short events late in the day are fully visible.** An event like 23:58–23:59 used to be cut down to a sliver at the bottom of the day, week and 3-day views. It now keeps its usual minimum height and sits flush against midnight ([#483]).
|
||||
- **The month view no longer crashes after going before 1900** in the day or week view ([#457]).
|
||||
- **Month pages keep updating.** After a small swipe that snapped back, a month page could stop showing new or edited events, and opening an event from the split view could show its old times ([#440], [#454]).
|
||||
- **Smoother expand and collapse in the split month view.** The day cells morph cleanly between the two layouts, and week numbers now animate along with them ([#473]).
|
||||
|
||||
## [2.23.1] — 2026-10-28
|
||||
|
||||
@@ -1946,5 +1947,6 @@ automatically, with zero telemetry and no internet permission.
|
||||
[#454]: https://codeberg.org/jlmakiola/calendula/issues/454
|
||||
[#457]: https://codeberg.org/jlmakiola/calendula/issues/457
|
||||
[#463]: https://codeberg.org/jlmakiola/calendula/issues/463
|
||||
[#473]: https://codeberg.org/jlmakiola/calendula/issues/473
|
||||
[#476]: https://codeberg.org/jlmakiola/calendula/issues/476
|
||||
[#483]: https://codeberg.org/jlmakiola/calendula/issues/483
|
||||
@@ -34,6 +34,10 @@ internal sealed interface MonthMorphKey {
|
||||
|
||||
data class DayNumber(val date: LocalDate) : MonthMorphKey
|
||||
|
||||
data class WeekNumber(val weekStart: LocalDate) : MonthMorphKey
|
||||
|
||||
data class CellRipple(val date: LocalDate) : MonthMorphKey
|
||||
|
||||
/**
|
||||
* One event *on one day*. Keyed by date as well as instance because a
|
||||
* multi-day event has a dot on every day it covers but only one bar, drawn
|
||||
@@ -111,8 +115,8 @@ internal fun morphInFlight(): Boolean {
|
||||
*
|
||||
* Rendering in place puts everything back in one z-order and one clip, which is
|
||||
* what makes the grid read as a single surface changing shape rather than a
|
||||
* stack of pieces sliding past each other. Nothing here needs to escape its
|
||||
* ancestors: the cells, marks and markers all travel within the grid.
|
||||
* stack of pieces sliding past each other. Event marks are the exception: they
|
||||
* render in the overlay so the expanding day backgrounds cannot cover them.
|
||||
*/
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
@@ -141,6 +145,7 @@ internal fun Modifier.morphElement(key: MonthMorphKey): Modifier {
|
||||
@Composable
|
||||
internal fun Modifier.morphBounds(key: MonthMorphKey): Modifier {
|
||||
val morph = LocalMonthMorph.current ?: return this
|
||||
val isEvent = key is MonthMorphKey.Event
|
||||
return with(morph.shared) {
|
||||
this@morphBounds.sharedBounds(
|
||||
sharedContentState = rememberSharedContentState(key),
|
||||
@@ -148,7 +153,8 @@ internal fun Modifier.morphBounds(key: MonthMorphKey): Modifier {
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds,
|
||||
renderInOverlayDuringTransition = false,
|
||||
renderInOverlayDuringTransition = isEvent,
|
||||
zIndexInOverlay = if (isEvent) 1f else 0f,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,12 @@ package de.jeanlucmakiola.calendula.ui.month
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.indication
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
import androidx.compose.animation.EnterExitState
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.foundation.gestures.detectVerticalDragGestures
|
||||
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
@@ -1226,7 +1231,6 @@ private fun SplitMonthContent(
|
||||
onEventClick: (EventInstance) -> Unit,
|
||||
onCreateEvent: (LocalDate) -> Unit,
|
||||
) {
|
||||
val fadeSpec = rememberCalendarFadeSpec()
|
||||
val reduceMotion = rememberReduceMotion()
|
||||
val haptics = LocalHapticFeedback.current
|
||||
BackHandler(enabled = expanded != initiallyExpanded) {
|
||||
@@ -1297,8 +1301,9 @@ private fun SplitMonthContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
// Both faces fill the same page — the grid grows into exactly the
|
||||
// room the pane gives up — so there is no size change to contain.
|
||||
// The travel between them is the shared elements' job.
|
||||
transitionSpec = { fadeIn(fadeSpec) togetherWith fadeOut(fadeSpec) },
|
||||
// Fading the faces made the shared cell backgrounds briefly
|
||||
// disappear beneath their incoming counterparts.
|
||||
transitionSpec = { EnterTransition.None togetherWith ExitTransition.None },
|
||||
label = "split-expand",
|
||||
) { isExpanded ->
|
||||
// The view switch's morph stays off a face until it has fully
|
||||
@@ -1355,6 +1360,9 @@ private fun SplitMonthContent(
|
||||
state = page,
|
||||
paneReady = paneReady,
|
||||
selected = pageSelected,
|
||||
selectionIndicatorVisible = reduceMotion ||
|
||||
(transition.currentState == EnterExitState.Visible &&
|
||||
transition.targetState == EnterExitState.Visible),
|
||||
showWeekNumbers = showWeekNumbers,
|
||||
showToday = showToday,
|
||||
expandDrag = expandDrag,
|
||||
@@ -1398,6 +1406,7 @@ private fun SplitMonthCollapsed(
|
||||
state: MonthUiState.Success,
|
||||
paneReady: Boolean,
|
||||
selected: LocalDate?,
|
||||
selectionIndicatorVisible: Boolean,
|
||||
showWeekNumbers: Boolean,
|
||||
showToday: Boolean,
|
||||
expandDrag: Modifier,
|
||||
@@ -1419,6 +1428,7 @@ private fun SplitMonthCollapsed(
|
||||
SplitMonthGrid(
|
||||
state = state,
|
||||
selected = selected,
|
||||
selectionIndicatorVisible = selectionIndicatorVisible,
|
||||
showWeekNumbers = showWeekNumbers,
|
||||
onSelectDay = onSelectDay,
|
||||
modifier = Modifier.height(splitGridHeight(state.weeks.size)),
|
||||
@@ -1626,6 +1636,7 @@ private val SPLIT_PANE_EASING = CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f)
|
||||
internal fun SplitMonthGrid(
|
||||
state: MonthUiState.Success,
|
||||
selected: LocalDate?,
|
||||
selectionIndicatorVisible: Boolean = true,
|
||||
showWeekNumbers: Boolean,
|
||||
onSelectDay: (LocalDate) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -1671,6 +1682,7 @@ internal fun SplitMonthGrid(
|
||||
// removes that without timing anything against the
|
||||
// page transition.
|
||||
isSelected = day == selected && inMonth,
|
||||
selectionIndicatorVisible = selectionIndicatorVisible,
|
||||
inMonth = inMonth,
|
||||
relativeToToday = todayCol?.let { col - it },
|
||||
rippleProgress = { ripple.value },
|
||||
@@ -1699,6 +1711,7 @@ private fun SplitDayCell(
|
||||
hidden: List<EventInstance>,
|
||||
isToday: Boolean,
|
||||
isSelected: Boolean,
|
||||
selectionIndicatorVisible: Boolean,
|
||||
inMonth: Boolean,
|
||||
relativeToToday: Int?,
|
||||
rippleProgress: () -> Float,
|
||||
@@ -1728,7 +1741,7 @@ private fun SplitDayCell(
|
||||
// pace it lingered after the thing it marks had already moved.
|
||||
val outlineSpec = MaterialTheme.motionScheme.fastEffectsSpec<Float>()
|
||||
val selection = animateFloatAsState(
|
||||
targetValue = if (isSelected) 1f else 0f,
|
||||
targetValue = if (isSelected && selectionIndicatorVisible) 1f else 0f,
|
||||
animationSpec = if (reduceMotion) snap() else outlineSpec,
|
||||
label = "split-day-selection",
|
||||
)
|
||||
@@ -1740,12 +1753,19 @@ private fun SplitDayCell(
|
||||
// built — and so the pill can be tagged for the morph on its own. Tagged as
|
||||
// one piece with its contents inside, the dots would be nested shared
|
||||
// elements within a shared element and travel twice.
|
||||
// The cell keeps the click and selection semantics, so TalkBack reads the
|
||||
// day number with them; the topmost layer only draws the ripple.
|
||||
val interaction = remember { MutableInteractionSource() }
|
||||
Box(
|
||||
modifier = modifier
|
||||
.padding(horizontal = CELL_GAP, vertical = 1.dp)
|
||||
.todayRipple(relativeToToday, rippleProgress)
|
||||
.clip(CELL_SHAPE)
|
||||
.selectable(selected = isSelected, onClick = onClick),
|
||||
.selectable(
|
||||
selected = isSelected,
|
||||
interactionSource = interaction,
|
||||
indication = null,
|
||||
onClick = onClick,
|
||||
),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
) {
|
||||
Box(
|
||||
@@ -1807,6 +1827,13 @@ private fun SplitDayCell(
|
||||
Spacer(Modifier.height(2.dp))
|
||||
SplitDots(date = date, events = events, hidden = hidden, dark = dark)
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.morphElement(MonthMorphKey.CellRipple(date))
|
||||
.clip(CELL_SHAPE)
|
||||
.indication(interaction, LocalIndication.current),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2532,6 +2559,7 @@ private fun MonthWeekRow(
|
||||
}
|
||||
}
|
||||
.padding(horizontal = CELL_GAP, vertical = 1.dp)
|
||||
.morphElement(MonthMorphKey.CellRipple(d))
|
||||
.clip(CELL_SHAPE)
|
||||
.clickable {
|
||||
// Cleared on read: a click with no fresh down
|
||||
@@ -2682,6 +2710,7 @@ private fun WeekNumberGutter(weekStart: LocalDate, modifier: Modifier = Modifier
|
||||
Box(
|
||||
modifier = modifier
|
||||
.padding(horizontal = CELL_GAP, vertical = 1.dp)
|
||||
.morphElement(MonthMorphKey.WeekNumber(weekStart))
|
||||
.clip(CELL_SHAPE)
|
||||
.background(MaterialTheme.colorScheme.secondaryContainer)
|
||||
.then(if (onOpenWeek != null) Modifier.clickable(onClickLabel = openLabel) {
|
||||
|
||||
Reference in new issue
Block a user