Keep resize handles alive mid-gesture and follow a moved occurrence (#141)
This commit is contained in:
@@ -109,9 +109,10 @@ data class TimelineDrag(
|
||||
|
||||
/**
|
||||
* The event a long press left selected (#141): lifted on the grid, with the
|
||||
* action bar up. [date] is the column the press landed in.
|
||||
* action bar up. [date] is the column the press landed in. [moved] holds from a
|
||||
* move or resize of it until the grid shows where it landed.
|
||||
*/
|
||||
data class TimelineSelection(val event: EventInstance, val date: LocalDate)
|
||||
data class TimelineSelection(val event: EventInstance, val date: LocalDate, val moved: Boolean = false)
|
||||
|
||||
/**
|
||||
* One day column's share of a dragged event, ready to draw. Both halves of an
|
||||
@@ -357,8 +358,8 @@ class TimelineDragController {
|
||||
var selection: TimelineSelection? by mutableStateOf(null)
|
||||
private set
|
||||
|
||||
fun select(event: EventInstance, date: LocalDate) {
|
||||
selection = TimelineSelection(event, date)
|
||||
fun select(event: EventInstance, date: LocalDate, moved: Boolean = false) {
|
||||
selection = TimelineSelection(event, date, moved)
|
||||
}
|
||||
|
||||
fun deselect() {
|
||||
@@ -368,12 +369,38 @@ class TimelineDragController {
|
||||
/**
|
||||
* Whether [block] is the selected event. Matched on the series and start too,
|
||||
* since the provider regenerates `Instances` rows and a re-read can bring the
|
||||
* same occurrence back under a new instance id.
|
||||
* same occurrence back under a new instance id — and, just after a move, on
|
||||
* what the event says, since moving one occurrence of a series writes it out
|
||||
* as an exception row with an id of its own. Only then: two copies of one
|
||||
* event would otherwise both match, and take the selection back and forth.
|
||||
* A blank title matches nothing that way.
|
||||
*/
|
||||
fun isSelected(block: TimedBlock): Boolean {
|
||||
val selected = selection?.event ?: return false
|
||||
return block.event.instanceId == selected.instanceId ||
|
||||
(block.event.eventId == selected.eventId && block.event.start == selected.start)
|
||||
val current = selection ?: return false
|
||||
val selected = current.event
|
||||
val event = block.event
|
||||
return event.instanceId == selected.instanceId ||
|
||||
(event.eventId == selected.eventId && event.start == selected.start) ||
|
||||
(
|
||||
current.moved &&
|
||||
event.calendarId == selected.calendarId &&
|
||||
event.title.isNotBlank() &&
|
||||
event.title == selected.title &&
|
||||
event.start == selected.start &&
|
||||
event.end == selected.end
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Take over [block]'s event as the selection once the grid shows it, so the
|
||||
* bar acts on the row that is there now — an exception written by a move has
|
||||
* its own id, and an abandoned move left the original times standing.
|
||||
*/
|
||||
fun adoptSelection(block: TimedBlock) {
|
||||
val current = selection ?: return
|
||||
if (current.event != block.event || current.moved) {
|
||||
selection = current.copy(event = block.event, moved = false)
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether a finger is on a block right now — [settling] is not dragging. */
|
||||
|
||||
@@ -532,6 +532,7 @@ private fun WeekContent(
|
||||
dragController.select(
|
||||
drop.event.copy(start = start, end = start + (drop.event.end - drop.event.start)),
|
||||
start.toLocalDateTime(zone).date,
|
||||
moved = true,
|
||||
)
|
||||
}
|
||||
move?.move(
|
||||
@@ -549,6 +550,7 @@ private fun WeekContent(
|
||||
dragController.select(
|
||||
drop.event.copy(start = drop.start, end = drop.end),
|
||||
drop.start.toLocalDateTime(zone).date,
|
||||
moved = true,
|
||||
)
|
||||
move?.move(
|
||||
MoveRequest(
|
||||
@@ -1580,9 +1582,16 @@ private fun EventBlock(
|
||||
val lifted = draggable && dragController.ghosts(block)
|
||||
val ghost = ghostAlpha(lifted)
|
||||
val raise = selectionLift(selected)
|
||||
// Handles on the selected event's own ends — not on a cut at midnight — while
|
||||
// nothing is in flight. A read-only event is selected but keeps its length.
|
||||
val showHandles = selected && draggable && !lifted && !dragController.isDragging
|
||||
// Not while the block is waiting on its own write: until then it still
|
||||
// shows the times from before it.
|
||||
LaunchedEffect(selected, block.event, lifted) {
|
||||
if (selected && !lifted) dragController.adoptSelection(block)
|
||||
}
|
||||
// Handles on the selected event's own ends — not on a cut at midnight. They
|
||||
// stay composed for the whole selection and only hide while something is in
|
||||
// flight: removing them would cancel the resize they started.
|
||||
val showHandles = selected && draggable
|
||||
val handlesShown = !lifted && !dragController.isDragging
|
||||
Box(
|
||||
modifier = (if (dimmed && !selected) modifier.alpha(EventDimAlpha) else modifier)
|
||||
.zIndex(if (selected) 1f else 0f)
|
||||
@@ -1632,6 +1641,7 @@ private fun EventBlock(
|
||||
}
|
||||
}
|
||||
val end = { dragController.finishResize(zone)?.let(dragController.onResize) }
|
||||
val handleAlpha = if (handlesShown) Modifier else Modifier.alpha(0f)
|
||||
if (!cuts.top) {
|
||||
ResizeHandle(
|
||||
color = accent,
|
||||
@@ -1641,7 +1651,8 @@ private fun EventBlock(
|
||||
onCancel = dragController::cancel,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.offset(x = HANDLE_INSET - ResizeHandleTarget / 2, y = -ResizeHandleTarget / 2),
|
||||
.offset(x = HANDLE_INSET - ResizeHandleTarget / 2, y = -ResizeHandleTarget / 2)
|
||||
.then(handleAlpha),
|
||||
)
|
||||
}
|
||||
if (!cuts.bottom) {
|
||||
@@ -1653,7 +1664,8 @@ private fun EventBlock(
|
||||
onCancel = dragController::cancel,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.offset(x = ResizeHandleTarget / 2 - HANDLE_INSET, y = ResizeHandleTarget / 2),
|
||||
.offset(x = ResizeHandleTarget / 2 - HANDLE_INSET, y = ResizeHandleTarget / 2)
|
||||
.then(handleAlpha),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user