diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimelineDrag.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimelineDrag.kt index 18449cd8..90616ad5 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimelineDrag.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimelineDrag.kt @@ -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. */ diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt index b8d6f574..df27411c 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt @@ -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), ) } }