Crossfade a block's time label (#68)

The block keeps its identity across a move and slides to the new slot, so
its label was the one thing left changing in a single frame.
This commit is contained in:
2026-08-02 18:43:02 +02:00
parent 4412038caa
commit 1285bbbf47
3 changed files with 40 additions and 10 deletions

View File

@@ -1,16 +1,50 @@
package de.jeanlucmakiola.calendula.ui.common
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.snap
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
/**
* A timed block's own time label, crossfaded rather than replaced. The block
* keeps its identity across a move and slides to the new slot; the label is the
* one thing on it that would otherwise change in a single frame.
*/
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun BlockTimeLabel(label: String, color: Color, modifier: Modifier = Modifier) {
val spec: FiniteAnimationSpec<Float> = if (rememberReduceMotion()) {
snap()
} else {
MaterialTheme.motionScheme.fastEffectsSpec()
}
Crossfade(
targetState = label,
animationSpec = spec,
label = "block-time",
modifier = modifier,
) { text ->
Text(
text = text,
style = MaterialTheme.typography.labelSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = color,
)
}
}
/** Where a timed block sits in its column, after tweening. */
@Immutable
data class BlockPlacement(val x: Dp, val y: Dp, val width: Dp, val height: Dp)

View File

@@ -80,6 +80,7 @@ import de.jeanlucmakiola.calendula.ui.common.TodayAction
import de.jeanlucmakiola.calendula.ui.common.CalendarFailure
import de.jeanlucmakiola.calendula.ui.common.CalendarView
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
import de.jeanlucmakiola.calendula.ui.common.BlockTimeLabel
import de.jeanlucmakiola.calendula.ui.common.animatedBlockPlacement
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
@@ -762,11 +763,8 @@ private fun EventBlock(
)
}
if (showTime) {
Text(
text = timeLabel,
style = MaterialTheme.typography.labelSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
BlockTimeLabel(
label = timeLabel,
color = eventInk(fill, alpha = SECONDARY_INK_ALPHA),
)
}

View File

@@ -89,6 +89,7 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarFailure
import de.jeanlucmakiola.calendula.ui.common.CalendarView
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
import de.jeanlucmakiola.calendula.ui.common.BlockTimeLabel
import de.jeanlucmakiola.calendula.ui.common.animatedBlockPlacement
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
@@ -931,11 +932,8 @@ private fun EventBlock(
)
}
if (showTime) {
Text(
text = timeLabel,
style = MaterialTheme.typography.labelSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
BlockTimeLabel(
label = timeLabel,
color = eventInk(fill, alpha = SECONDARY_INK_ALPHA),
)
}