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 package de.jeanlucmakiola.calendula.ui.common
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.snap import androidx.compose.animation.core.snap
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue 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 androidx.compose.ui.unit.Dp
import de.jeanlucmakiola.floret.identity.rememberReduceMotion 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. */ /** Where a timed block sits in its column, after tweening. */
@Immutable @Immutable
data class BlockPlacement(val x: Dp, val y: Dp, val width: Dp, val height: Dp) 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.CalendarFailure
import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.CalendarView
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS 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.animatedBlockPlacement
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
@@ -762,11 +763,8 @@ private fun EventBlock(
) )
} }
if (showTime) { if (showTime) {
Text( BlockTimeLabel(
text = timeLabel, label = timeLabel,
style = MaterialTheme.typography.labelSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = eventInk(fill, alpha = SECONDARY_INK_ALPHA), 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.CalendarView
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha 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.animatedBlockPlacement
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
@@ -931,11 +932,8 @@ private fun EventBlock(
) )
} }
if (showTime) { if (showTime) {
Text( BlockTimeLabel(
text = timeLabel, label = timeLabel,
style = MaterialTheme.typography.labelSmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = eventInk(fill, alpha = SECONDARY_INK_ALPHA), color = eventInk(fill, alpha = SECONDARY_INK_ALPHA),
) )
} }