Move the chip into floret-kit, and tween the blocks (#68)
The confirmation pill is now floret-kit's SnackChip, shared with Agendula's swipe-delete undo, wearing the FAB's colours and crossfading when its message is answered. Timed blocks are keyed by event and their bounds tweened, so a landing drop, the neighbour that has to make room for it, and an undo all slide instead of popping. The source ghost travels with them rather than fading out, and the floating copy dissolves into the block once the grid has caught up.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
|
||||
|
||||
/** 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)
|
||||
|
||||
/**
|
||||
* A timed block's placement, tweened rather than jumped. Every bound a block
|
||||
* has changes for a reason the user just caused — a drop landing at a new time,
|
||||
* an undo putting it back, a neighbour arriving and halving both lanes — and all
|
||||
* of them read better as motion than as a new layout appearing.
|
||||
*
|
||||
* Continuity comes from the caller keying each block by identity; a block
|
||||
* composed for the first time starts *at* its target, so nothing flies in from
|
||||
* the corner on the first frame.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun animatedBlockPlacement(x: Dp, y: Dp, width: Dp, height: Dp): BlockPlacement {
|
||||
val spec: FiniteAnimationSpec<Dp> = if (rememberReduceMotion()) {
|
||||
snap()
|
||||
} else {
|
||||
MaterialTheme.motionScheme.fastSpatialSpec()
|
||||
}
|
||||
val animatedX by animateDpAsState(x, spec, label = "block-x")
|
||||
val animatedY by animateDpAsState(y, spec, label = "block-y")
|
||||
val animatedWidth by animateDpAsState(width, spec, label = "block-width")
|
||||
val animatedHeight by animateDpAsState(height, spec, label = "block-height")
|
||||
return BlockPlacement(animatedX, animatedY, animatedWidth, animatedHeight)
|
||||
}
|
||||
@@ -1,25 +1,13 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.FloatingActionButtonDefaults
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -29,14 +17,12 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.LiveRegionMode
|
||||
import androidx.compose.ui.semantics.liveRegion
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import de.jeanlucmakiola.calendula.R
|
||||
import de.jeanlucmakiola.floret.components.SnackChip
|
||||
import de.jeanlucmakiola.floret.components.SnackChipHeight
|
||||
import de.jeanlucmakiola.floret.components.SnackChipMargin
|
||||
import de.jeanlucmakiola.floret.locale.currentLocale
|
||||
import de.jeanlucmakiola.floret.locale.localizedDateFormatter
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -51,8 +37,6 @@ private const val CHIP_MILLIS = 4_000L
|
||||
/** The FAB's own band at the bottom end, which the chip must not run into. */
|
||||
private val FAB_BAND = 88.dp
|
||||
|
||||
private val CHIP_HEIGHT = 56.dp
|
||||
|
||||
/**
|
||||
* The two surfaces a drag-and-drop reschedule needs on top of the calendar: the
|
||||
* recurring-scope prompt, and the confirmation chip carrying Undo.
|
||||
@@ -113,78 +97,26 @@ fun EventMoveHost(viewModel: RescheduleViewModel, modifier: Modifier = Modifier)
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.navigationBarsPadding()
|
||||
.padding(start = 16.dp, bottom = 16.dp)
|
||||
.height(CHIP_HEIGHT),
|
||||
.padding(start = SnackChipMargin, bottom = SnackChipMargin)
|
||||
.height(SnackChipHeight),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
MoveChip(
|
||||
SnackChip(
|
||||
visible = outcome != null,
|
||||
message = shownMessage,
|
||||
maxWidth = chipMaxWidth,
|
||||
onUndo = shownUndo?.let { undo -> { viewModel.undo(undo) } },
|
||||
actionLabel = stringResource(R.string.event_move_undo)
|
||||
.takeIf { shownUndo != null },
|
||||
onAction = shownUndo?.let { undo -> { viewModel.undo(undo) } },
|
||||
// The FAB's own colours: the chip shares its band, so sharing its
|
||||
// fill reads as one bottom layer rather than two competing ones.
|
||||
color = FloatingActionButtonDefaults.containerColor,
|
||||
contentColor = contentColorFor(FloatingActionButtonDefaults.containerColor),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The confirmation pill: sized to its content, sliding up from the bottom, with
|
||||
* Undo where the write has a clean inverse.
|
||||
*/
|
||||
@Composable
|
||||
private fun MoveChip(
|
||||
visible: Boolean,
|
||||
message: String,
|
||||
maxWidth: Dp,
|
||||
onUndo: (() -> Unit)?,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = slideInVertically { it } + fadeIn(),
|
||||
exit = slideOutVertically { it } + fadeOut(),
|
||||
) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
shape = RoundedCornerShape(50),
|
||||
shadowElevation = 6.dp,
|
||||
modifier = Modifier.widthIn(max = maxWidth),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.padding(
|
||||
start = 20.dp,
|
||||
end = if (onUndo != null) 8.dp else 20.dp,
|
||||
top = 6.dp,
|
||||
bottom = 6.dp,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = message,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.semantics { liveRegion = LiveRegionMode.Polite },
|
||||
)
|
||||
if (onUndo != null) {
|
||||
TextButton(
|
||||
onClick = onUndo,
|
||||
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 6.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.event_move_undo),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "Fri, 7 Aug, 09:00" — the day, plus the time for a timed event. All-day events
|
||||
* are read back on the UTC calendar day they are anchored to (#65, #82).
|
||||
|
||||
@@ -62,17 +62,15 @@ fun moveInFlight(): Boolean {
|
||||
const val GHOST_ALPHA: Float = 0.3f
|
||||
|
||||
/**
|
||||
* Opacity for a block whose copy is in flight: ghosted while the finger holds
|
||||
* it, then faded out once the copy has landed, so the slot it came from is
|
||||
* already empty by the time the grid re-reads the move.
|
||||
* Opacity for a block whose copy is in flight: ghosted from the lift until the
|
||||
* copy is handed back, then animated up rather than switched. The ghost keeps
|
||||
* its place through the write and *travels* to the new slot when the grid
|
||||
* re-reads it, arriving under the copy as that fades — so what the eye follows
|
||||
* is one block moving, not one vanishing and another appearing.
|
||||
*/
|
||||
@Composable
|
||||
fun ghostAlpha(lifted: Boolean, landed: Boolean): Float = animateFloatAsState(
|
||||
targetValue = when {
|
||||
!lifted -> 1f
|
||||
landed -> 0f
|
||||
else -> GHOST_ALPHA
|
||||
},
|
||||
fun ghostAlpha(lifted: Boolean): Float = animateFloatAsState(
|
||||
targetValue = if (lifted) GHOST_ALPHA else 1f,
|
||||
label = "ghost-alpha",
|
||||
).value
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.MutatePriority
|
||||
import androidx.compose.foundation.background
|
||||
@@ -300,11 +301,19 @@ fun TimelineDrop.startInstant(zone: TimeZone): Instant =
|
||||
LocalDateTime(date, LocalTime(startMin / 60, startMin % 60)).toInstant(zone)
|
||||
|
||||
/**
|
||||
* How long a landed block keeps its position after the write reports back. The
|
||||
* provider's change notification and the re-query land a beat later; releasing
|
||||
* on the write alone would still flash the old slot.
|
||||
* How long a landed block keeps its position after the write reports back — the
|
||||
* provider's change notification and the re-query land a beat later, and
|
||||
* releasing on the write alone would still flash the old slot.
|
||||
*/
|
||||
const val SETTLE_GRACE_MILLIS: Long = 450L
|
||||
const val SETTLE_GRACE_MILLIS: Long = 200L
|
||||
|
||||
/**
|
||||
* The hand-over: the copy dissolves over this while the grid's own block slides
|
||||
* in under it, so the two overlap rather than one replacing the other. Kept
|
||||
* short, because a full-width copy sits over any neighbour it now shares a lane
|
||||
* with until it is gone.
|
||||
*/
|
||||
const val SETTLE_FADE_MILLIS: Int = 250
|
||||
|
||||
/**
|
||||
* The floating block, drawn over the whole calendar so it is free of the day
|
||||
@@ -328,10 +337,14 @@ fun TimelineDragOverlay(controller: TimelineDragController, modifier: Modifier =
|
||||
if (controller.isDragging) controller.autoScroll()
|
||||
}
|
||||
// Hold the landed copy until the write is done — including the whole time a
|
||||
// recurring drop's scope dialog is up — then a grace for the re-read.
|
||||
// recurring drop's scope dialog is up — then a grace for the re-read, then
|
||||
// fade it into the grid's own block, which has slid in underneath by now.
|
||||
var handingOver by remember(controller.settling) { mutableStateOf(false) }
|
||||
LaunchedEffect(controller.settling, moveInFlight) {
|
||||
if (controller.settling == null || moveInFlight) return@LaunchedEffect
|
||||
delay(SETTLE_GRACE_MILLIS)
|
||||
handingOver = true
|
||||
delay(SETTLE_FADE_MILLIS.toLong())
|
||||
controller.release()
|
||||
}
|
||||
|
||||
@@ -351,6 +364,11 @@ fun TimelineDragOverlay(controller: TimelineDragController, modifier: Modifier =
|
||||
targetValue = if (landed || reduceMotion) 0f else 1f,
|
||||
label = "drag-lift",
|
||||
)
|
||||
val copyAlpha by animateFloatAsState(
|
||||
targetValue = if (handingOver) 0f else 1f,
|
||||
animationSpec = tween(SETTLE_FADE_MILLIS),
|
||||
label = "drag-handover",
|
||||
)
|
||||
val fill = eventFill(drag.event.color, dark, soften)
|
||||
val title = drag.event.title.ifBlank { stringResource(R.string.event_untitled) }
|
||||
val label = "${formatMinuteOfDay(drag.startMin, use24Hour, locale)}–" +
|
||||
@@ -374,6 +392,7 @@ fun TimelineDragOverlay(controller: TimelineDragController, modifier: Modifier =
|
||||
scaleX = 1f + 0.02f * lift
|
||||
scaleY = 1f + 0.02f * lift
|
||||
shadowElevation = 8.dp.toPx() * lift
|
||||
alpha = copyAlpha
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
clip = false
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@@ -79,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.animatedBlockPlacement
|
||||
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
|
||||
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
|
||||
import de.jeanlucmakiola.calendula.ui.common.MoveRequest
|
||||
@@ -640,25 +642,39 @@ private fun DayColumnCard(
|
||||
) {
|
||||
val colWidth = maxWidth
|
||||
val minEventHeight = hourHeight * MIN_EVENT_FRACTION
|
||||
// Keyed by event, so a block that changes time or lane is the *same*
|
||||
// composable afterwards and tweens there. The ordinal disambiguates
|
||||
// the rare column holding two occurrences of one series, which would
|
||||
// otherwise be two blocks under one key.
|
||||
val ordinals = mutableMapOf<Long, Int>()
|
||||
blocks.forEach { block ->
|
||||
val laneWidth = colWidth / block.laneCount
|
||||
val top = hourHeight * (block.startMin / 60f)
|
||||
val rawHeight = hourHeight * ((block.endMin - block.startMin) / 60f)
|
||||
val height = if (rawHeight < minEventHeight) minEventHeight else rawHeight
|
||||
EventBlock(
|
||||
block = block,
|
||||
dark = dark,
|
||||
height = height,
|
||||
date = date,
|
||||
dragController = dragController,
|
||||
onClick = { onEventClick(block.event) },
|
||||
onDrop = onDrop,
|
||||
modifier = Modifier
|
||||
.offset(x = laneWidth * block.lane, y = top)
|
||||
.width(laneWidth)
|
||||
.height(height)
|
||||
.padding(horizontal = 1.dp),
|
||||
)
|
||||
val ordinal = ordinals.merge(block.event.eventId, 1, Int::plus)!! - 1
|
||||
key(block.event.eventId, ordinal) {
|
||||
val laneWidth = colWidth / block.laneCount
|
||||
val top = hourHeight * (block.startMin / 60f)
|
||||
val rawHeight = hourHeight * ((block.endMin - block.startMin) / 60f)
|
||||
val height = if (rawHeight < minEventHeight) minEventHeight else rawHeight
|
||||
val place = animatedBlockPlacement(
|
||||
x = laneWidth * block.lane,
|
||||
y = top,
|
||||
width = laneWidth,
|
||||
height = height,
|
||||
)
|
||||
EventBlock(
|
||||
block = block,
|
||||
dark = dark,
|
||||
height = place.height,
|
||||
date = date,
|
||||
dragController = dragController,
|
||||
onClick = { onEventClick(block.event) },
|
||||
onDrop = onDrop,
|
||||
modifier = Modifier
|
||||
.offset(x = place.x, y = place.y)
|
||||
.width(place.width)
|
||||
.height(place.height)
|
||||
.padding(horizontal = 1.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
// Current-time line, on top of the events, only on today's column.
|
||||
if (date == today) {
|
||||
@@ -714,12 +730,12 @@ private fun EventBlock(
|
||||
onCancel = dragController::cancel,
|
||||
)
|
||||
val lifted = draggable && dragController.liftedInstanceId == block.event.instanceId
|
||||
val ghost = ghostAlpha(lifted, landed = dragController.settling != null)
|
||||
val ghost = ghostAlpha(lifted)
|
||||
Box(
|
||||
modifier = modifier
|
||||
// The source stays put as a ghost while its floating copy travels,
|
||||
// then fades out as the copy settles on its new slot.
|
||||
.then(if (lifted) Modifier.alpha(ghost) else Modifier)
|
||||
.then(if (ghost < 1f) Modifier.alpha(ghost) else Modifier)
|
||||
.background(fill, RoundedCornerShape(4.dp))
|
||||
.clickable(onClick = onClick)
|
||||
// After clickable, so it is the inner node and wins the main pass;
|
||||
|
||||
@@ -2266,7 +2266,7 @@ private fun MonthBar(
|
||||
// fades out as the copy settles on its new day.
|
||||
val monthDrag = LocalMonthDrag.current
|
||||
val lifted = monthDrag?.liftedInstanceId == event.instanceId
|
||||
val ghost = ghostAlpha(lifted, landed = monthDrag?.settling != null)
|
||||
val ghost = ghostAlpha(lifted)
|
||||
val shape = RoundedCornerShape(
|
||||
topStart = if (continuesLeft) 0.dp else 4.dp,
|
||||
bottomStart = if (continuesLeft) 0.dp else 4.dp,
|
||||
@@ -2275,7 +2275,7 @@ private fun MonthBar(
|
||||
)
|
||||
Box(
|
||||
modifier = (if (dimmed) modifier.alpha(EventDimAlpha) else modifier)
|
||||
.then(if (lifted) Modifier.alpha(ghost) else Modifier)
|
||||
.then(if (ghost < 1f) Modifier.alpha(ghost) else Modifier)
|
||||
.background(fill, shape)
|
||||
.padding(horizontal = 4.dp)
|
||||
.semantics {
|
||||
|
||||
@@ -47,6 +47,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -88,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.animatedBlockPlacement
|
||||
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
|
||||
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
|
||||
import de.jeanlucmakiola.calendula.ui.common.MoveRequest
|
||||
@@ -788,26 +790,40 @@ private fun DayColumnCard(
|
||||
) {
|
||||
val colWidth = maxWidth
|
||||
val minEventHeight = hourHeight * MIN_EVENT_FRACTION
|
||||
// Keyed by event, so a block that changes time or lane is the *same*
|
||||
// composable afterwards and tweens there. The ordinal disambiguates
|
||||
// the rare column holding two occurrences of one series, which would
|
||||
// otherwise be two blocks under one key.
|
||||
val ordinals = mutableMapOf<Long, Int>()
|
||||
blocks.forEach { block ->
|
||||
val laneWidth = colWidth / block.laneCount
|
||||
val top = hourHeight * (block.startMin / 60f)
|
||||
val rawHeight = hourHeight * ((block.endMin - block.startMin) / 60f)
|
||||
val height = if (rawHeight < minEventHeight) minEventHeight else rawHeight
|
||||
EventBlock(
|
||||
block = block,
|
||||
dark = dark,
|
||||
height = height,
|
||||
width = laneWidth,
|
||||
date = date,
|
||||
dragController = dragController,
|
||||
onClick = { onEventClick(block.event) },
|
||||
onDrop = onDrop,
|
||||
modifier = Modifier
|
||||
.offset(x = laneWidth * block.lane, y = top)
|
||||
.width(laneWidth)
|
||||
.height(height)
|
||||
.padding(horizontal = 1.dp),
|
||||
)
|
||||
val ordinal = ordinals.merge(block.event.eventId, 1, Int::plus)!! - 1
|
||||
key(block.event.eventId, ordinal) {
|
||||
val laneWidth = colWidth / block.laneCount
|
||||
val top = hourHeight * (block.startMin / 60f)
|
||||
val rawHeight = hourHeight * ((block.endMin - block.startMin) / 60f)
|
||||
val height = if (rawHeight < minEventHeight) minEventHeight else rawHeight
|
||||
val place = animatedBlockPlacement(
|
||||
x = laneWidth * block.lane,
|
||||
y = top,
|
||||
width = laneWidth,
|
||||
height = height,
|
||||
)
|
||||
EventBlock(
|
||||
block = block,
|
||||
dark = dark,
|
||||
height = place.height,
|
||||
width = place.width,
|
||||
date = date,
|
||||
dragController = dragController,
|
||||
onClick = { onEventClick(block.event) },
|
||||
onDrop = onDrop,
|
||||
modifier = Modifier
|
||||
.offset(x = place.x, y = place.y)
|
||||
.width(place.width)
|
||||
.height(place.height)
|
||||
.padding(horizontal = 1.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
// Current-time line, on top of the events, only on today's column.
|
||||
if (date == today) {
|
||||
@@ -883,12 +899,12 @@ private fun EventBlock(
|
||||
onCancel = dragController::cancel,
|
||||
)
|
||||
val lifted = draggable && dragController.liftedInstanceId == block.event.instanceId
|
||||
val ghost = ghostAlpha(lifted, landed = dragController.settling != null)
|
||||
val ghost = ghostAlpha(lifted)
|
||||
Box(
|
||||
modifier = (if (dimmed) modifier.alpha(EventDimAlpha) else modifier)
|
||||
// The source stays put as a ghost while its floating copy travels,
|
||||
// then fades out as the copy settles on its new slot.
|
||||
.then(if (lifted) Modifier.alpha(ghost) else Modifier)
|
||||
.then(if (ghost < 1f) Modifier.alpha(ghost) else Modifier)
|
||||
.background(fill, RoundedCornerShape(4.dp))
|
||||
.clickable(onClick = onClick)
|
||||
// After clickable, so it is the inner node and wins the main pass;
|
||||
|
||||
Submodule floret-kit updated: ed1d3ca5e8...0ce855c925
Reference in New Issue
Block a user