@@ -3,6 +3,7 @@ package de.jeanlucmakiola.calendula.ui.month
|
|||||||
import androidx.compose.animation.AnimatedVisibilityScope
|
import androidx.compose.animation.AnimatedVisibilityScope
|
||||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
import androidx.compose.animation.SharedTransitionScope
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
|
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -97,9 +98,11 @@ internal fun Modifier.morphElement(key: MonthMorphKey): Modifier {
|
|||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
internal fun Modifier.morphOverlay(zIndex: Float = 1f): Modifier {
|
internal fun Modifier.morphOverlay(
|
||||||
|
zIndex: Float = 1f,
|
||||||
|
fadeSpec: FiniteAnimationSpec<Float> = rememberCalendarFadeSpec(),
|
||||||
|
): Modifier {
|
||||||
val morph = LocalMonthMorph.current ?: return this
|
val morph = LocalMonthMorph.current ?: return this
|
||||||
val fadeSpec = rememberCalendarFadeSpec()
|
|
||||||
val lifted = with(morph.shared) {
|
val lifted = with(morph.shared) {
|
||||||
this@morphOverlay.renderInSharedTransitionScopeOverlay(zIndexInOverlay = zIndex)
|
this@morphOverlay.renderInSharedTransitionScopeOverlay(zIndexInOverlay = zIndex)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1313,10 +1313,13 @@ private fun SplitDayCell(
|
|||||||
// 42 cells at once, which is what made the outline shiver rather than fade.
|
// 42 cells at once, which is what made the outline shiver rather than fade.
|
||||||
// Kept in the draw phase, the animation touches nothing but the pixels.
|
// Kept in the draw phase, the animation touches nothing but the pixels.
|
||||||
val reduceMotion = rememberReduceMotion()
|
val reduceMotion = rememberReduceMotion()
|
||||||
val fadeSpec = rememberCalendarFadeSpec()
|
// The outline runs on the *fast* effects spec, unlike everything else here.
|
||||||
|
// It is a small mark that only ever appears or disappears, and at the shared
|
||||||
|
// pace it lingered after the thing it marks had already moved.
|
||||||
|
val outlineSpec = MaterialTheme.motionScheme.fastEffectsSpec<Float>()
|
||||||
val selection = animateFloatAsState(
|
val selection = animateFloatAsState(
|
||||||
targetValue = if (isSelected) 1f else 0f,
|
targetValue = if (isSelected) 1f else 0f,
|
||||||
animationSpec = if (reduceMotion) snap() else fadeSpec,
|
animationSpec = if (reduceMotion) snap() else outlineSpec,
|
||||||
label = "split-day-selection",
|
label = "split-day-selection",
|
||||||
)
|
)
|
||||||
val outlineColor = MaterialTheme.colorScheme.primary
|
val outlineColor = MaterialTheme.colorScheme.primary
|
||||||
@@ -1354,7 +1357,7 @@ private fun SplitDayCell(
|
|||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.morphOverlay()
|
.morphOverlay(fadeSpec = outlineSpec)
|
||||||
.drawBehind {
|
.drawBehind {
|
||||||
val alpha = selection.value
|
val alpha = selection.value
|
||||||
if (alpha <= 0f) return@drawBehind
|
if (alpha <= 0f) return@drawBehind
|
||||||
@@ -1408,18 +1411,29 @@ private fun SplitDots(date: LocalDate, events: List<EventInstance>, hidden: Int,
|
|||||||
// The dot keeps its own circle and the bar its 4dp corners; they
|
// The dot keeps its own circle and the bar its 4dp corners; they
|
||||||
// cross-fade inside shared bounds, and at the dot's size the two
|
// cross-fade inside shared bounds, and at the dot's size the two
|
||||||
// radii are a fraction of a pixel apart.
|
// radii are a fraction of a pixel apart.
|
||||||
|
//
|
||||||
|
// morphBounds goes *outside* the size: the shared bounds have to be
|
||||||
|
// free to drive the measurement. Behind a fixed .size() the dot stayed
|
||||||
|
// 5dp for the whole transition however far its bar had travelled, and
|
||||||
|
// only snapped to full width once the transition ended — the growth
|
||||||
|
// never happened, it was clamped away.
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(SPLIT_DOT_SIZE)
|
|
||||||
.morphBounds(MonthMorphKey.Event(date, event.instanceId))
|
.morphBounds(MonthMorphKey.Event(date, event.instanceId))
|
||||||
|
.size(SPLIT_DOT_SIZE)
|
||||||
.background(eventFill(event.color, dark, soften), CircleShape),
|
.background(eventFill(event.color, dark, soften), CircleShape),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (hidden > 0) {
|
if (hidden > 0) {
|
||||||
|
// Lifted into the overlay for the same reason the selection outline
|
||||||
|
// is: untagged content stays in the regular tree, which the tagged
|
||||||
|
// pieces paint over wholesale, so it vanished for the length of the
|
||||||
|
// transition and arrived a beat after everything else had settled.
|
||||||
Text(
|
Text(
|
||||||
text = "+$hidden",
|
text = "+$hidden",
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.morphOverlay(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1728,19 +1742,25 @@ private fun MonthWeekRow(
|
|||||||
x = colW * span.startCol,
|
x = colW * span.startCol,
|
||||||
y = EVENT_ROW_HEIGHT * span.lane,
|
y = EVENT_ROW_HEIGHT * span.lane,
|
||||||
)
|
)
|
||||||
.width(colW * cols)
|
|
||||||
.height(EVENT_ROW_HEIGHT)
|
|
||||||
.padding(horizontal = CELL_GAP + 1.dp, vertical = 1.dp)
|
|
||||||
// Anchored on the day the bar starts *in this row*,
|
// Anchored on the day the bar starts *in this row*,
|
||||||
// which is where its dot sat. A bar carried in from
|
// which is where its dot sat. A bar carried in from
|
||||||
// the previous week starts at column 0, and column
|
// the previous week starts at column 0, and column
|
||||||
// 0's dot is the one that grows into it.
|
// 0's dot is the one that grows into it.
|
||||||
|
//
|
||||||
|
// Outside the width/height, so the shared bounds
|
||||||
|
// drive the measurement instead of being pinned to
|
||||||
|
// the bar's final size from the first frame. The
|
||||||
|
// offset stays outside it: that is where the bar
|
||||||
|
// sits, not how big it is.
|
||||||
.morphBounds(
|
.morphBounds(
|
||||||
MonthMorphKey.Event(
|
MonthMorphKey.Event(
|
||||||
date = week.days[span.startCol],
|
date = week.days[span.startCol],
|
||||||
instanceId = span.event.instanceId,
|
instanceId = span.event.instanceId,
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
|
.width(colW * cols)
|
||||||
|
.height(EVENT_ROW_HEIGHT)
|
||||||
|
.padding(horizontal = CELL_GAP + 1.dp, vertical = 1.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Single-day timed pills + overflow, per column. Pills fill the
|
// Single-day timed pills + overflow, per column. Pills fill the
|
||||||
@@ -1766,10 +1786,10 @@ private fun MonthWeekRow(
|
|||||||
x = colW * col,
|
x = colW * col,
|
||||||
y = EVENT_ROW_HEIGHT * freeSlots[i],
|
y = EVENT_ROW_HEIGHT * freeSlots[i],
|
||||||
)
|
)
|
||||||
|
.morphBounds(MonthMorphKey.Event(d, ev.instanceId))
|
||||||
.width(colW)
|
.width(colW)
|
||||||
.height(EVENT_ROW_HEIGHT)
|
.height(EVENT_ROW_HEIGHT)
|
||||||
.padding(horizontal = CELL_GAP + 1.dp, vertical = 1.dp)
|
.padding(horizontal = CELL_GAP + 1.dp, vertical = 1.dp),
|
||||||
.morphBounds(MonthMorphKey.Event(d, ev.instanceId)),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val hidden = (week.countByDay[d] ?: 0) - occupied.size - pillsShown.size
|
val hidden = (week.countByDay[d] ?: 0) - occupied.size - pillsShown.size
|
||||||
@@ -1786,6 +1806,10 @@ private fun MonthWeekRow(
|
|||||||
dark = dark,
|
dark = dark,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.offset(x = colW * col, y = EVENT_ROW_HEIGHT * MAX_EVENT_ROWS)
|
.offset(x = colW * col, y = EVENT_ROW_HEIGHT * MAX_EVENT_ROWS)
|
||||||
|
// Untagged, so lifted into the overlay or it
|
||||||
|
// spends the transition painted over — see the
|
||||||
|
// compact grid's "+N".
|
||||||
|
.morphOverlay()
|
||||||
.width(colW)
|
.width(colW)
|
||||||
.padding(horizontal = 3.dp),
|
.padding(horizontal = 3.dp),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user