Carry a month chip back on undo (#68)
Undo moves the event just as the drop did, so it now travels the same way instead of the chip vanishing off the new day and reappearing on the old one. The write signals when it starts, the controller puts a copy on the chip's current seat, and the settle that follows is the drop's own — glide onto the restored seat, ghost underneath, hand over there. A week row now publishes what it draws along with where it draws it, so the controller can find any moved event's seat itself rather than each row scanning its own lanes.
This commit is contained in:
@@ -317,6 +317,7 @@ fun CalendarHost(
|
|||||||
movableCalendarIds = movableCalendarIds,
|
movableCalendarIds = movableCalendarIds,
|
||||||
move = reschedule::move,
|
move = reschedule::move,
|
||||||
inFlight = reschedule.inFlight,
|
inFlight = reschedule.inFlight,
|
||||||
|
undoStarted = reschedule.undoStarted,
|
||||||
edit = onEditEvent,
|
edit = onEditEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ package de.jeanlucmakiola.calendula.ui.common
|
|||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.CustomAccessibilityAction
|
import androidx.compose.ui.semantics.CustomAccessibilityAction
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
@@ -38,6 +42,8 @@ class EventMoveScope(
|
|||||||
* and replacing it would recompose all of them twice per drop.
|
* and replacing it would recompose all of them twice per drop.
|
||||||
*/
|
*/
|
||||||
val inFlight: StateFlow<Boolean>,
|
val inFlight: StateFlow<Boolean>,
|
||||||
|
/** Ticks when an undo write begins — see `RescheduleViewModel.undoStarted`. */
|
||||||
|
val undoStarted: StateFlow<Int>,
|
||||||
/** Open an event in the edit form — the pointer-free route to the same change. */
|
/** Open an event in the edit form — the pointer-free route to the same change. */
|
||||||
val edit: (EventInstance) -> Unit,
|
val edit: (EventInstance) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -48,6 +54,8 @@ val LocalEventMove = compositionLocalOf<EventMoveScope?> { null }
|
|||||||
|
|
||||||
private val NEVER_IN_FLIGHT = MutableStateFlow(false)
|
private val NEVER_IN_FLIGHT = MutableStateFlow(false)
|
||||||
|
|
||||||
|
private val NEVER_UNDONE = MutableStateFlow(0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether a dropped event is still being written — false wherever moving is off.
|
* Whether a dropped event is still being written — false wherever moving is off.
|
||||||
* The drag overlays hold a landed block on its target for this window.
|
* The drag overlays hold a landed block on its target for this window.
|
||||||
@@ -58,6 +66,23 @@ fun moveInFlight(): Boolean {
|
|||||||
return flow.collectAsStateWithLifecycle().value
|
return flow.collectAsStateWithLifecycle().value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs [onUndo] when an undo write begins, and never for one that began before
|
||||||
|
* this composable came on screen — a view switched to *after* an undo has
|
||||||
|
* nothing left to carry back.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun OnUndoStarted(onUndo: () -> Unit) {
|
||||||
|
val flow = LocalEventMove.current?.undoStarted ?: NEVER_UNDONE
|
||||||
|
val tick by flow.collectAsStateWithLifecycle()
|
||||||
|
var seen by remember { mutableIntStateOf(tick) }
|
||||||
|
LaunchedEffect(tick) {
|
||||||
|
if (tick == seen) return@LaunchedEffect
|
||||||
|
seen = tick
|
||||||
|
onUndo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Opacity the source block keeps while its floating copy travels. */
|
/** Opacity the source block keeps while its floating copy travels. */
|
||||||
const val GHOST_ALPHA: Float = 0.3f
|
const val GHOST_ALPHA: Float = 0.3f
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,17 @@ class RescheduleViewModel @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
val inFlight: StateFlow<Boolean> = _inFlight.asStateFlow()
|
val inFlight: StateFlow<Boolean> = _inFlight.asStateFlow()
|
||||||
|
|
||||||
|
private val _undoStarted = MutableStateFlow(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ticks the moment an undo write begins — before the provider has anything to
|
||||||
|
* re-read. An undo moves an event exactly as a drop does, so the view that
|
||||||
|
* drew the drop takes this as its cue to carry the chip back rather than let
|
||||||
|
* it reappear on the old day. A counter rather than the undo itself: what a
|
||||||
|
* view needs is the *timing*, and it already knows what it moved.
|
||||||
|
*/
|
||||||
|
val undoStarted: StateFlow<Int> = _undoStarted.asStateFlow()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set from the moment a drop is accepted until its write settles. Two drops
|
* Set from the moment a drop is accepted until its write settles. Two drops
|
||||||
* of the same recurring event landing inside that window would each compute
|
* of the same recurring event landing inside that window would each compute
|
||||||
@@ -235,6 +246,7 @@ class RescheduleViewModel @Inject constructor(
|
|||||||
fun undo(undo: MoveUndo) {
|
fun undo(undo: MoveUndo) {
|
||||||
if (busy) return
|
if (busy) return
|
||||||
busy = true
|
busy = true
|
||||||
|
_undoStarted.value += 1
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_outcome.value = try {
|
_outcome.value = try {
|
||||||
repository.updateEvent(undo.eventId, undo.moved, undo.restored)
|
repository.updateEvent(undo.eventId, undo.moved, undo.restored)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.layout.LayoutCoordinates
|
import androidx.compose.ui.layout.LayoutCoordinates
|
||||||
import androidx.compose.ui.layout.boundsInRoot
|
import androidx.compose.ui.layout.boundsInRoot
|
||||||
|
import androidx.compose.ui.layout.positionInRoot
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
@@ -20,14 +21,49 @@ class MonthRowGeometry(
|
|||||||
val days: List<LocalDate>,
|
val days: List<LocalDate>,
|
||||||
/** The row's day-column box — the space chip offsets are measured in. */
|
/** The row's day-column box — the space chip offsets are measured in. */
|
||||||
val cell: LayoutCoordinates,
|
val cell: LayoutCoordinates,
|
||||||
|
/** The lane band inside that box, where the chips themselves are seated. */
|
||||||
|
val band: LayoutCoordinates?,
|
||||||
val columnWidthPx: Float,
|
val columnWidthPx: Float,
|
||||||
|
val laneHeightPx: Float,
|
||||||
|
val laneCount: Int,
|
||||||
/**
|
/**
|
||||||
* Whether the columns are laid out right-to-left. Pointer coordinates are
|
* Whether the columns are laid out right-to-left. Pointer coordinates are
|
||||||
* never mirrored, but the grid is, so the leftmost column is the *last* day
|
* never mirrored, but the grid is, so the leftmost column is the *last* day
|
||||||
* in Arabic.
|
* in Arabic.
|
||||||
*/
|
*/
|
||||||
val isRtl: Boolean,
|
val isRtl: Boolean,
|
||||||
)
|
/** What this row currently draws in [lane] of column `col`, or null. */
|
||||||
|
val chipAt: (col: Int, lane: Int) -> EventInstance?,
|
||||||
|
) {
|
||||||
|
/** Root top-left of the chip seated in [lane] of column [col]. */
|
||||||
|
fun seat(col: Int, lane: Int): Offset? {
|
||||||
|
val band = band?.takeIf { it.isAttached } ?: return null
|
||||||
|
val origin = band.positionInRoot()
|
||||||
|
val column = if (isRtl) days.lastIndex - col else col
|
||||||
|
return Offset(origin.x + column * columnWidthPx, origin.y + lane * laneHeightPx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where this row seats [event] on [date], or null when it doesn't hold it —
|
||||||
|
* the day isn't in this week, or the chip went into the day's "+N" overflow.
|
||||||
|
*/
|
||||||
|
fun seatOf(event: EventInstance, date: LocalDate): Offset? {
|
||||||
|
val col = days.indexOf(date).takeIf { it >= 0 } ?: return null
|
||||||
|
val lane = (0 until laneCount).firstOrNull { lane ->
|
||||||
|
chipAt(col, lane)?.let { isSameEvent(it, event) } == true
|
||||||
|
} ?: return null
|
||||||
|
return seat(col, lane)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether [chip] is the moved event [moved] as the grid now holds it. Neither
|
||||||
|
* id alone will do: the provider hands re-read instances new instance ids, and a
|
||||||
|
* single-occurrence move writes an exception row with a new event id — the title
|
||||||
|
* is what survives both.
|
||||||
|
*/
|
||||||
|
private fun isSameEvent(chip: EventInstance, moved: EventInstance): Boolean =
|
||||||
|
chip.eventId == moved.eventId || chip.title == moved.title
|
||||||
|
|
||||||
/** A chip in flight, in root coordinates so it can be drawn in an overlay. */
|
/** A chip in flight, in root coordinates so it can be drawn in an overlay. */
|
||||||
data class MonthChipDrag(
|
data class MonthChipDrag(
|
||||||
@@ -105,6 +141,13 @@ class MonthDragController {
|
|||||||
var isDragging: Boolean by mutableStateOf(false)
|
var isDragging: Boolean by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last drop this controller made, kept past [release] for [beginUndo] —
|
||||||
|
* the confirmation chip carries Undo for four seconds after the copy is long
|
||||||
|
* gone. Not snapshot state: nothing draws from it.
|
||||||
|
*/
|
||||||
|
private var undoable: MonthChipDrag? = null
|
||||||
|
|
||||||
private var event: EventInstance? = null
|
private var event: EventInstance? = null
|
||||||
private var grabDate: LocalDate? = null
|
private var grabDate: LocalDate? = null
|
||||||
private var grab = Offset.Zero
|
private var grab = Offset.Zero
|
||||||
@@ -164,20 +207,46 @@ class MonthDragController {
|
|||||||
fun isSettledChip(event: EventInstance, days: List<LocalDate>?): Boolean {
|
fun isSettledChip(event: EventInstance, days: List<LocalDate>?): Boolean {
|
||||||
val landed = settling?.takeIf { settledGhost } ?: return false
|
val landed = settling?.takeIf { settledGhost } ?: return false
|
||||||
if (days == null || landed.targetDate !in days) return false
|
if (days == null || landed.targetDate !in days) return false
|
||||||
return event.eventId == landed.event.eventId || event.title == landed.event.title
|
return isSameEvent(event, landed.event)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish a seat. The continuous style can show the same week twice, once in
|
* Ask the row keyed [token] whether it now seats the settled chip, and take
|
||||||
* each adjoining month, so both copies of the landing row offer one: the one
|
* its answer. The continuous style can show the same week twice, once in each
|
||||||
* nearest where the chip was dropped is the one the finger was over.
|
* adjoining month, so both copies of the landing row will answer: the seat
|
||||||
|
* nearest where the chip was let go is the one the finger was over.
|
||||||
*/
|
*/
|
||||||
fun noteSettled(topLeftInRoot: Offset) {
|
fun noteSettled(token: Any) {
|
||||||
val landed = settling ?: return
|
val landed = settling ?: return
|
||||||
|
val target = landed.targetDate ?: return
|
||||||
|
val at = rows[token]?.seatOf(landed.event, target) ?: return
|
||||||
val current = settledInRoot
|
val current = settledInRoot
|
||||||
val closer = current == null ||
|
val closer = current == null ||
|
||||||
abs(topLeftInRoot.y - landed.topLeftInRoot.y) < abs(current.y - landed.topLeftInRoot.y)
|
abs(at.y - landed.topLeftInRoot.y) < abs(current.y - landed.topLeftInRoot.y)
|
||||||
if (closer) settledInRoot = topLeftInRoot
|
if (closer) settledInRoot = at
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a copy back for an undo, which moves the event exactly as the drop did
|
||||||
|
* and so should read the same way rather than teleporting the chip. Nothing
|
||||||
|
* here writes anything: it puts a chip on the journey the inverse write is
|
||||||
|
* about to make, and the settle that follows is the drop's own.
|
||||||
|
*
|
||||||
|
* False when there is nothing to carry — no drop of this controller's to undo
|
||||||
|
* (it happened in another view, or has already been undone), or the grid does
|
||||||
|
* not seat the moved event where it would have to start from. The chip then
|
||||||
|
* simply reappears on the day it came from, as it always did.
|
||||||
|
*/
|
||||||
|
fun beginUndo(): Boolean {
|
||||||
|
val last = undoable ?: return false
|
||||||
|
undoable = null
|
||||||
|
val from = last.targetDate ?: return false
|
||||||
|
val at = rows.values.firstNotNullOfOrNull { it.seatOf(last.event, from) } ?: return false
|
||||||
|
settling = last.copy(grabDate = from, targetDate = last.grabDate, topLeftInRoot = at)
|
||||||
|
liftedInstanceId = last.event.instanceId
|
||||||
|
settledGhost = true
|
||||||
|
settledInRoot = null
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -195,6 +264,7 @@ class MonthDragController {
|
|||||||
)
|
)
|
||||||
liftedInstanceId = landed.event.instanceId
|
liftedInstanceId = landed.event.instanceId
|
||||||
settledGhost = true
|
settledGhost = true
|
||||||
|
undoable = settling
|
||||||
return MonthChipDrop(landed.event, landed.grabDate, target)
|
return MonthChipDrop(landed.event, landed.grabDate, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.material3.rememberDrawerState
|
import androidx.compose.material3.rememberDrawerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -92,6 +93,7 @@ import de.jeanlucmakiola.calendula.ui.common.MoveTarget
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.MoveRequest
|
import de.jeanlucmakiola.calendula.ui.common.MoveRequest
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
|
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
|
||||||
import de.jeanlucmakiola.calendula.ui.common.moveInFlight
|
import de.jeanlucmakiola.calendula.ui.common.moveInFlight
|
||||||
|
import de.jeanlucmakiola.calendula.ui.common.OnUndoStarted
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
|
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
|
||||||
import de.jeanlucmakiola.calendula.ui.common.DragSnapHaptics
|
import de.jeanlucmakiola.calendula.ui.common.DragSnapHaptics
|
||||||
import de.jeanlucmakiola.calendula.ui.common.SETTLE_FADE_MILLIS
|
import de.jeanlucmakiola.calendula.ui.common.SETTLE_FADE_MILLIS
|
||||||
@@ -416,6 +418,10 @@ fun MonthScreen(
|
|||||||
// overlay so it is free of the week row's clip and, in the scrolling
|
// overlay so it is free of the week row's clip and, in the scrolling
|
||||||
// styles, of the list viewport's.
|
// styles, of the list viewport's.
|
||||||
val chipDrag = rememberMonthDragController()
|
val chipDrag = rememberMonthDragController()
|
||||||
|
// Undo moves the event back, so the chip travels back too rather than
|
||||||
|
// reappearing on its old day. The signal comes from the write, not
|
||||||
|
// from the chip that offers it: that lives above every view.
|
||||||
|
OnUndoStarted { chipDrag.beginUndo() }
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
@@ -1860,6 +1866,7 @@ private fun MonthWeekRow(
|
|||||||
val dragController = LocalMonthDrag.current
|
val dragController = LocalMonthDrag.current
|
||||||
val rowToken = remember { Any() }
|
val rowToken = remember { Any() }
|
||||||
val bandCoordinates = remember { arrayOfNulls<LayoutCoordinates>(1) }
|
val bandCoordinates = remember { arrayOfNulls<LayoutCoordinates>(1) }
|
||||||
|
val cellCoordinates = remember { arrayOfNulls<LayoutCoordinates>(1) }
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val rowHeightPx = with(density) { EVENT_ROW_HEIGHT.toPx() }
|
val rowHeightPx = with(density) { EVENT_ROW_HEIGHT.toPx() }
|
||||||
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
|
||||||
@@ -1867,37 +1874,36 @@ private fun MonthWeekRow(
|
|||||||
DisposableEffect(rowToken, dragController) {
|
DisposableEffect(rowToken, dragController) {
|
||||||
onDispose { dragController?.removeRow(rowToken) }
|
onDispose { dragController?.removeRow(rowToken) }
|
||||||
}
|
}
|
||||||
// Where this row seats a drop that landed on one of its days, once the
|
// Republished on layout *and* on every recomposition: the coordinates only
|
||||||
// re-read arrives — the copy in flight glides onto it. Null until the grid
|
// change on the former, but what the row draws — which the controller reads
|
||||||
// actually holds the moved event: before the re-read the week still shows it
|
// to find a moved chip's seat — changes on the latter, and a re-read that
|
||||||
// on the day it came from, and a chip that lands in the "+N" overflow gets no
|
// moves an event doesn't move the row it lands in.
|
||||||
// seat at all.
|
val publish = {
|
||||||
val landed = dragController?.settling
|
val cell = cellCoordinates[0]?.takeIf { it.isAttached }
|
||||||
val seat = if (landed == null) {
|
if (dragController != null && cell != null) {
|
||||||
null
|
dragController.putRow(
|
||||||
} else {
|
rowToken,
|
||||||
remember(week, landed) {
|
MonthRowGeometry(
|
||||||
val target = landed.targetDate ?: return@remember null
|
days = week.days,
|
||||||
val col = week.days.indexOf(target).takeIf { it >= 0 } ?: return@remember null
|
cell = cell,
|
||||||
val lane = (0 until MAX_EVENT_ROWS).firstOrNull { lane ->
|
band = bandCoordinates[0],
|
||||||
val chip = week.chipAt(col, lane, MAX_EVENT_ROWS)
|
columnWidthPx = cell.size.width / 7f,
|
||||||
chip != null && dragController.isSettledChip(chip, listOf(target))
|
laneHeightPx = rowHeightPx,
|
||||||
} ?: return@remember null
|
laneCount = MAX_EVENT_ROWS,
|
||||||
col to lane
|
isRtl = isRtl,
|
||||||
}
|
chipAt = { col, lane -> week.chipAt(col, lane, MAX_EVENT_ROWS) },
|
||||||
}
|
|
||||||
LaunchedEffect(seat, dragController) {
|
|
||||||
val (col, lane) = seat ?: return@LaunchedEffect
|
|
||||||
val band = bandCoordinates[0]?.takeIf { it.isAttached } ?: return@LaunchedEffect
|
|
||||||
val origin = band.positionInRoot()
|
|
||||||
val column = if (isRtl) week.days.lastIndex - col else col
|
|
||||||
dragController?.noteSettled(
|
|
||||||
Offset(
|
|
||||||
x = origin.x + column * (band.size.width / 7f),
|
|
||||||
y = origin.y + lane * rowHeightPx,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
SideEffect { publish() }
|
||||||
|
// Once the grid holds the settled chip, tell the controller where this row
|
||||||
|
// has seated it, so the copy in flight can glide onto it. Keyed on the week
|
||||||
|
// because that is what changes when the re-read lands: until then the row
|
||||||
|
// still draws the event on the day it came from and answers nothing.
|
||||||
|
LaunchedEffect(week, dragController?.settling) {
|
||||||
|
dragController?.noteSettled(rowToken)
|
||||||
|
}
|
||||||
|
|
||||||
Row(modifier) {
|
Row(modifier) {
|
||||||
// Optional calendar-week gutter, sized so the seven day columns below
|
// Optional calendar-week gutter, sized so the seven day columns below
|
||||||
@@ -1916,15 +1922,8 @@ private fun MonthWeekRow(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.onGloballyPositioned { coords ->
|
.onGloballyPositioned { coords ->
|
||||||
dragController?.putRow(
|
cellCoordinates[0] = coords
|
||||||
rowToken,
|
publish()
|
||||||
MonthRowGeometry(
|
|
||||||
days = week.days,
|
|
||||||
cell = coords,
|
|
||||||
columnWidthPx = coords.size.width / 7f,
|
|
||||||
isRtl = isRtl,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.then(
|
.then(
|
||||||
monthChipDragModifier(
|
monthChipDragModifier(
|
||||||
@@ -2000,7 +1999,10 @@ private fun MonthWeekRow(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.onGloballyPositioned { bandCoordinates[0] = it }
|
.onGloballyPositioned {
|
||||||
|
bandCoordinates[0] = it
|
||||||
|
publish()
|
||||||
|
}
|
||||||
// A dragged chip travels to another row, so the clip has
|
// A dragged chip travels to another row, so the clip has
|
||||||
// to yield for it exactly as it does for a morph.
|
// to yield for it exactly as it does for a morph.
|
||||||
.then(if (morphing || dragging) Modifier else Modifier.clipToBounds()),
|
.then(if (morphing || dragging) Modifier else Modifier.clipToBounds()),
|
||||||
|
|||||||
Reference in New Issue
Block a user