From 97dbdcad0cfa48bfb44bdb0627af1b1a3d21a70e Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Fri, 9 Oct 2026 19:09:06 +0200 Subject: [PATCH] Fix second review round on selection, resize and the FAB (#141) --- .../calendula/ui/common/CalendarChrome.kt | 9 ++- .../calendula/ui/common/CalendarFabColumn.kt | 11 ++- .../calendula/ui/common/EventMoveHost.kt | 11 +-- .../calendula/ui/common/EventSelectionBar.kt | 2 +- .../calendula/ui/common/EventSelectionHost.kt | 77 +++++++++++-------- .../calendula/ui/common/TimelineDrag.kt | 76 +++++++++++++----- .../calendula/ui/week/WeekScreen.kt | 32 ++++++-- .../calendula/ui/week/WeekViewPreview.kt | 1 + 8 files changed, 148 insertions(+), 71 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarChrome.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarChrome.kt index 41acd8fe..508dd5f8 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarChrome.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarChrome.kt @@ -90,8 +90,6 @@ class ChromeHost { var selectionCompact by mutableStateOf(false) internal set - /** Whether the selection's action bar is showing — it steps aside mid-gesture. */ - val selectionBarShown: Boolean get() = selection != null && !selectionBusy var actions: ScreenActions? = null private set @@ -124,7 +122,12 @@ fun PublishSelection(event: EventInstance?, busy: Boolean, compact: Boolean, onD val current = slot.current DisposableEffect(host, selection, current) { if (current) host.selection = selection - onDispose { if (host.selection === selection) host.selection = null } + onDispose { + if (host.selection === selection) host.selection = null + // Left mid-drag, nothing else would clear these for the other views. + host.selectionBusy = false + host.selectionCompact = false + } } if (current) { SideEffect { diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarFabColumn.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarFabColumn.kt index 71a6e3be..63e576fd 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarFabColumn.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarFabColumn.kt @@ -24,6 +24,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -36,7 +37,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import de.jeanlucmakiola.calendula.R +/** The standard FAB's size, shared by the selection bar that unrolls from it (#141). */ +val FabSize = 56.dp + /** What the FAB is while an event is selected (#141): the toggle for its action bar. */ +@Stable class FabSelection( val expanded: Boolean, val onToggle: () -> Unit, @@ -44,8 +49,6 @@ class FabSelection( ) -/** The standard FAB's size, which the row it sits in is held to. */ -private val FAB_SIZE = 56.dp /** * The FAB stack shared by the three calendar views: a persistent "+" to @@ -80,10 +83,10 @@ fun CalendarFabColumn( } // Held past the selection clearing, so the bar has something to fold away. val heldBar = remember { mutableStateOf<(@Composable () -> Unit)?>(null) } - selection?.let { heldBar.value = it.bar } + if (selection != null && heldBar.value !== selection.bar) heldBar.value = selection.bar // Held at the FAB's height so the taller bar can't shift the FAB. Row( - modifier = Modifier.height(FAB_SIZE), + modifier = Modifier.height(FabSize), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventMoveHost.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventMoveHost.kt index 0ab8a4a9..abf39782 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventMoveHost.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventMoveHost.kt @@ -106,16 +106,17 @@ fun EventMoveHost( // tapped in the last moments of the window leaves the outcome at Moved on // purpose, and this timer would otherwise fire mid-undo and close the chip // just before the same chip has to say "undone". - LaunchedEffect(actionFailure) { - if (actionFailure == null) return@LaunchedEffect - delay(CHIP_MILLIS) - onActionFailureShown() - } LaunchedEffect(outcome, undoTick, writeInFlight) { if (outcome == null || writeInFlight) return@LaunchedEffect delay(if (outcome == MoveOutcome.Undone) UNDONE_CHIP_MILLIS else CHIP_MILLIS) viewModel.consumeOutcome() } + // A failure waits for the chip to be free, then has its own turn. + LaunchedEffect(actionFailure, outcome == null) { + if (actionFailure == null || outcome != null) return@LaunchedEffect + delay(CHIP_MILLIS) + onActionFailureShown() + } BoxWithConstraints(modifier = modifier.fillMaxSize()) { val chipMaxWidth = maxWidth - FAB_BAND diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventSelectionBar.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventSelectionBar.kt index 7e0a18eb..f161c52c 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventSelectionBar.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventSelectionBar.kt @@ -38,7 +38,7 @@ fun EventSelectionBar( color = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer, shadowElevation = 6.dp, - modifier = modifier.height(56.dp), + modifier = modifier.height(FabSize), ) { Row( verticalAlignment = Alignment.CenterVertically, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventSelectionHost.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventSelectionHost.kt index f2353536..34f4603c 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventSelectionHost.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventSelectionHost.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -17,7 +18,7 @@ import de.jeanlucmakiola.floret.components.SnackChipMargin import kotlinx.coroutines.launch /** The bottom band the selection bar and FAB cover, margin included. */ -val SelectionBarClearance = 56.dp + SnackChipMargin +val SelectionBarClearance = FabSize + SnackChipMargin /** The FAB's role while an event is selected (#141): its action bar's toggle. Null otherwise. */ @Composable @@ -40,45 +41,53 @@ fun rememberFabSelection( // Folded while a chip is up, which then sits beside the FAB as usual. val expanded = !chipShown && (chosen ?: !host.selectionCompact) + val currentEdit by rememberUpdatedState(onEdit) + val currentDuplicate by rememberUpdatedState(onDuplicate) + val currentDismissChip by rememberUpdatedState(onDismissChip) + val currentWrite by rememberUpdatedState(requireWrite) + val selection = host.selection ?: return null val event = selection.event val calendar = calendars[event.calendarId] val canEdit = calendar?.canModifyContents == true - return FabSelection( - expanded = expanded, - onToggle = { - if (chipShown) onDismissChip() - chosen = !expanded - }, - bar = { - EventSelectionBar( - canEdit = canEdit, - canDelete = canEdit && !calendar.isManaged, - onEdit = { - requireWrite { - selection.dismiss() - onEdit(event) - } - }, - onDuplicate = { - requireWrite { - scope.launch { - val form = viewModel.duplicateForm(event) ?: return@launch + val canDelete = canEdit && calendar?.isManaged == false + return remember(selection, expanded, chipShown, canEdit, canDelete) { + FabSelection( + expanded = expanded, + onToggle = { + if (chipShown) currentDismissChip() + chosen = !expanded + }, + bar = { + EventSelectionBar( + canEdit = canEdit, + canDelete = canDelete, + onEdit = { + currentWrite { selection.dismiss() - onDuplicate(form) + currentEdit(event) } - } - }, - onShare = { - scope.launch { - val uri = viewModel.shareUri(event) ?: return@launch - if (!context.sendEventShare(uri, chooserTitle)) viewModel.reportShareFailed() - } - }, - onDelete = { requireWrite { viewModel.requestDelete(event) } }, - ) - }, - ) + }, + onDuplicate = { + currentWrite { + scope.launch { + val form = viewModel.duplicateForm(event) ?: return@launch + selection.dismiss() + currentDuplicate(form) + } + } + }, + onShare = { + scope.launch { + val uri = viewModel.shareUri(event) ?: return@launch + if (!context.sendEventShare(uri, chooserTitle)) viewModel.reportShareFailed() + } + }, + onDelete = { currentWrite { viewModel.requestDelete(event) } }, + ) + }, + ) + } } /** What a failed action on a selected event says, for the move host's chip to show (#141). */ 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 ea3ec1fc..ac4a600b 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 @@ -21,7 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -38,6 +38,7 @@ import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize @@ -390,9 +391,6 @@ class TimelineDragController { */ private var settledOrigin: Pair? by mutableStateOf(null) - /** Where a finished resize is written — set by the screen that hosts this timeline. */ - var onResize: (TimelineResizeDrop) -> Unit = {} - /** The selected event, if any — see [TimelineSelection]. */ var selection: TimelineSelection? by mutableStateOf(null) private set @@ -407,12 +405,26 @@ class TimelineDragController { selectionCovered = false } - /** How many blocks on screen show the selected event; none for a while ends the selection. */ - var selectedBlocks: Int by mutableIntStateOf(0) - private set + /** The events of the blocks on screen that show the selection; none for a while ends it. */ + val selectedCandidates = mutableStateListOf() - fun noteSelectedBlock(shown: Boolean) { - selectedBlocks += if (shown) 1 else -1 + fun noteSelectedBlock(event: EventInstance, shown: Boolean) { + if (shown) selectedCandidates.add(event) else selectedCandidates.remove(event) + } + + /** + * Once a move of the selected event has settled, point the selection at the + * row it landed as. A new event id wins: a single moved occurrence is written + * as an exception, and another occurrence of its series may share its time. + */ + fun settleMovedSelection() { + val current = selection?.takeIf { it.moved } ?: return + if (isDragging || settling != null) return + val landed = selectedCandidates.filter { it.instanceId != current.event.instanceId } + // Only the original row left: the move was abandoned, and it still stands. + val chosen = landed.firstOrNull { it.eventId != current.event.eventId } + ?: landed.firstOrNull() ?: selectedCandidates.firstOrNull() ?: return + selection = current.copy(event = chosen, moved = false) } /** Whether the selected block's bottom sits where the action bar would cover it. */ @@ -441,16 +453,26 @@ class TimelineDragController { /** Point the selection at [block]'s event, so the bar acts on the row now there. */ fun adoptSelection(block: TimedBlock) { - val current = selection ?: return - if (current.event != block.event || current.moved) { - selection = current.copy(event = block.event, moved = false) - } + val current = selection?.takeIf { !it.moved } ?: return + if (current.event != block.event) selection = current.copy(event = block.event) } /** Whether a finger is on a block right now — [settling] is not dragging. */ var isDragging: Boolean by mutableStateOf(false) private set + /** Whether the finger has moved since the pickup; only then may the view auto-scroll. */ + var pointerMoved: Boolean by mutableStateOf(false) + private set + + /** Touch slop, past which the finger counts as having moved. */ + var slopPx: Float = 0f + + private var pickupPointer = Offset.Zero + + /** The end a settling resize asked for, which the grid has to show before it settles. */ + private var settledEnd: Instant? = null + /** * Whether the grid itself now draws the settled drop at its landing slot. * The copy may only be handed back once this is true, or the source ghost @@ -527,6 +549,8 @@ class TimelineDragController { liftedInstanceId = block.event.instanceId grab = pointerInRoot - blockInRoot pointer = pointerInRoot + pickupPointer = pointerInRoot + pointerMoved = false originSlot = null recompute() originSlot = drag?.slot @@ -534,6 +558,7 @@ class TimelineDragController { fun move(pointerInRoot: Offset) { pointer = pointerInRoot + if (!pointerMoved && (pointerInRoot - pickupPointer).getDistance() > slopPx) pointerMoved = true recompute() } @@ -546,12 +571,14 @@ class TimelineDragController { clipOffsetMin = 0 titleLines = 0 isDragging = false + pointerMoved = false liftedInstanceId = null originSlot = null drag = null settling = null settledOnGrid = false settledOrigin = null + settledEnd = null } /** @@ -582,10 +609,11 @@ class TimelineDragController { val offset = (date.toEpochDays() - landed.date.toEpochDays()).toInt() val slice = dragSlices(landed.eventStartMin, landed.eventSpanMin, offset..offset) .firstOrNull() ?: return + val end = settledEnd settledOnGrid = blocks.any { block -> block.startMin == slice.startMin && - // The end too: a bottom-edge resize keeps the start. - block.endMin == slice.startMin + slice.spanMin && + // A resize may keep its start, so its end has to have landed too. + (end == null || block.event.end == end) && ( block.event.eventId == landed.event.eventId || ( @@ -645,8 +673,14 @@ class TimelineDragController { this.titleLines = titleLines settling = null isDragging = true + pointerMoved = true liftedInstanceId = block.event.instanceId pointer = pointerInRoot + // Measured from the edge's own column: the handle overhangs into the next. + val edgeDay = if (edge == ResizeEdge.Top) 0 else (resizeEndMin - 1).floorDiv(MINUTES_PER_DAY).coerceAtLeast(0) + val edgeIndex = geometry.days.indexOf(start.date.plus(edgeDay, DateTimeUnit.DAY)) + resizeColumn = edgeIndex.takeIf { it >= 0 } + ?.let { if (geometry.isRtl) geometry.days.lastIndex - it else it } resizeGrabMin = (pointerResizeMinutes() ?: 0f) - if (edge == ResizeEdge.Top) resizeStartMin else resizeEndMin originSlot = null @@ -667,11 +701,13 @@ class TimelineDragController { liftedInstanceId = landed.event.instanceId settledOrigin = landed.event.eventId to landed.event.start settledAbandonedMark = abandonedTicks?.value ?: 0 - return TimelineResizeDrop( + val drop = TimelineResizeDrop( event = landed.event, start = wallClockInstant(landed.date, landed.eventStartMin, zone), end = wallClockInstant(landed.date, landed.eventStartMin + landed.eventSpanMin, zone), ) + settledEnd = drop.end + return drop } /** @@ -767,6 +803,7 @@ class TimelineDragController { fun release() { settling = null settledOnGrid = false + settledEnd = null handOver() } @@ -871,6 +908,7 @@ fun rememberTimelineDragController(): TimelineDragController { val density = LocalDensity.current controller.geometry.edgePx = with(density) { AUTO_SCROLL_EDGE.toPx() } controller.geometry.stepPx = with(density) { AUTO_SCROLL_STEP.toPx() } + controller.slopPx = LocalViewConfiguration.current.touchSlop controller.abandonedTicks = LocalEventMove.current?.abandoned return controller } @@ -953,8 +991,10 @@ fun TimelineDragOverlay(controller: TimelineDragController, modifier: Modifier = // Both live here rather than beside the controller: they read [drag], which // changes every frame, and this composable is the one that is meant to. DragSnapHaptics(controller.drag?.slot) - LaunchedEffect(controller.isDragging) { - if (controller.isDragging) controller.autoScroll() + // Only once the finger has moved: a long press held near an edge must not + // scroll the drop target out from under a finger that only means to select. + LaunchedEffect(controller.isDragging && controller.pointerMoved) { + if (controller.isDragging && controller.pointerMoved) controller.autoScroll() } // Hold the landed copy until the write is done — including the time a // recurring drop's scope dialog is up — and then until the grid draws the 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 4a50a327..55fc236b 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 @@ -548,7 +548,7 @@ private fun WeekContent( } // A resize keeps its event selected, at its new length (#141). - dragController.onResize = { drop: TimelineResizeDrop -> + val onResize: (TimelineResizeDrop) -> Unit = { drop -> dragController.select( drop.event.copy(start = drop.start, end = drop.end), moved = true, @@ -576,7 +576,10 @@ private fun WeekContent( snapshotFlow { pagerState.currentPage }.drop(1).collect { dragController.deselect() } } LaunchedEffect(morph != null) { if (morph != null) dragController.deselect() } - val orphaned = selection != null && dragController.selectedBlocks == 0 && + LaunchedEffect(selection, dragController.settling, dragController.isDragging, dragController.selectedCandidates.size) { + dragController.settleMovedSelection() + } + val orphaned = selection != null && dragController.selectedCandidates.isEmpty() && !dragController.isDragging && dragController.settling == null LaunchedEffect(orphaned) { if (!orphaned) return@LaunchedEffect @@ -613,6 +616,7 @@ private fun WeekContent( onOpenDay = onOpenDay, onCreateAt = onCreateAt, onDrop = onDrop, + onResize = onResize, ) } else WeekPager( pagerState = pagerState, @@ -627,6 +631,7 @@ private fun WeekContent( onOpenDay = onOpenDay, onCreateAt = onCreateAt, onDrop = onDrop, + onResize = onResize, ) } } @@ -653,6 +658,7 @@ private fun WeekPager( onOpenDay: (LocalDate) -> Unit, onCreateAt: (LocalDate, Int) -> Unit, onDrop: (TimelineDrop) -> Unit, + onResize: (TimelineResizeDrop) -> Unit, ) { val stripHeights = remember { mutableStateMapOf() } val allDayHeight = pagedHeight(pagerState, stripHeights, initialStripHeight) @@ -701,6 +707,7 @@ private fun WeekPager( onOpenDay = onOpenDay, onCreateAt = onCreateAt, onDrop = onDrop, + onResize = onResize, ) } } @@ -735,6 +742,7 @@ private fun TimelineMorphFrame( onOpenDay: (LocalDate) -> Unit, onCreateAt: (LocalDate, Int) -> Unit, onDrop: (TimelineDrop) -> Unit, + onResize: (TimelineResizeDrop) -> Unit, ) { val fromState by morph.fromState.collectAsStateWithLifecycle() val toState by morph.toState.collectAsStateWithLifecycle() @@ -790,6 +798,7 @@ private fun TimelineMorphFrame( onOpenDay = onOpenDay, onCreateAt = onCreateAt, onDrop = onDrop, + onResize = onResize, ) } } @@ -883,6 +892,7 @@ internal fun WeekSuccess( onOpenDay: (LocalDate) -> Unit, onCreateAt: (LocalDate, Int) -> Unit, onDrop: (TimelineDrop) -> Unit, + onResize: (TimelineResizeDrop) -> Unit, ) { WeekFrame( weekStart = state.weekStart, @@ -902,6 +912,7 @@ internal fun WeekSuccess( onOpenDay = onOpenDay, onCreateAt = onCreateAt, onDrop = onDrop, + onResize = onResize, ) } } @@ -924,6 +935,7 @@ private fun WeekPage( onOpenDay: (LocalDate) -> Unit, onCreateAt: (LocalDate, Int) -> Unit, onDrop: (TimelineDrop) -> Unit, + onResize: (TimelineResizeDrop) -> Unit, ) { val geometry = remember(state.weekStart, state.days.size) { ColumnGeometry.of(DaySpan(state.weekStart, state.days.size)) @@ -943,6 +955,7 @@ private fun WeekPage( onOpenDay = onOpenDay, onCreateAt = onCreateAt, onDrop = onDrop, + onResize = onResize, ) } @@ -966,6 +979,7 @@ private fun TimelinePageContent( onOpenDay: (LocalDate) -> Unit, onCreateAt: (LocalDate, Int) -> Unit, onDrop: (TimelineDrop) -> Unit, + onResize: (TimelineResizeDrop) -> Unit, ) { Column(modifier = Modifier.fillMaxSize()) { Column( @@ -996,6 +1010,7 @@ private fun TimelinePageContent( onEventClick = onEventClick, onCreateAt = onCreateAt, onDrop = onDrop, + onResize = onResize, ) } } @@ -1225,6 +1240,7 @@ private fun Timeline( onEventClick: (EventInstance) -> Unit, onCreateAt: (LocalDate, Int) -> Unit, onDrop: (TimelineDrop) -> Unit, + onResize: (TimelineResizeDrop) -> Unit, ) { val fullViewportHeight = remember(geometry) { intArrayOf(-1) } val dark = isSystemInDarkTheme() @@ -1322,6 +1338,7 @@ private fun Timeline( onEventClick = onEventClick, onCreateAt = onCreateAt, onDrop = onDrop, + onResize = onResize, scrollState = scrollState, modifier = Modifier.fillMaxHeight(), ) @@ -1344,6 +1361,7 @@ private fun DayColumnCard( onEventClick: (EventInstance) -> Unit, onCreateAt: (LocalDate, Int) -> Unit, onDrop: (TimelineDrop) -> Unit, + onResize: (TimelineResizeDrop) -> Unit, scrollState: ScrollState, modifier: Modifier = Modifier, ) { @@ -1455,6 +1473,7 @@ private fun DayColumnCard( dragController = dragController, onClick = { onEventClick(block.event) }, onDrop = onDrop, + onResize = onResize, modifier = Modifier .laneSlot(place) .offset(y = place.y + topInset) @@ -1499,6 +1518,7 @@ private fun EventBlock( dragController: TimelineDragController, onClick: () -> Unit, onDrop: (TimelineDrop) -> Unit, + onResize: (TimelineResizeDrop) -> Unit, modifier: Modifier = Modifier, ) { val title = block.event.title.ifBlank { stringResource(R.string.event_untitled) } @@ -1605,9 +1625,9 @@ private fun EventBlock( if (selected && !lifted) dragController.adoptSelection(block) } // Kept composed while hidden: removing them would cancel their own resize. - DisposableEffect(selected) { - if (selected) dragController.noteSelectedBlock(true) - onDispose { if (selected) dragController.noteSelectedBlock(false) } + DisposableEffect(selected, block.event) { + if (selected) dragController.noteSelectedBlock(block.event, true) + onDispose { if (selected) dragController.noteSelectedBlock(block.event, false) } } val showHandles = selected && draggable val handlesShown = !lifted && !dragController.isDragging @@ -1668,7 +1688,7 @@ private fun EventBlock( dragController.beginResize(block, edge, zone, handleLines, pointer) } } - val end = { dragController.finishResize(zone)?.let(dragController.onResize) } + val end = { dragController.finishResize(zone)?.let(onResize) } val handleIn by animateFloatAsState( targetValue = if (handlesShown) 1f else 0f, animationSpec = MaterialTheme.motionScheme.fastSpatialSpec(), diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewPreview.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewPreview.kt index d336cdf3..ace125e7 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewPreview.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewPreview.kt @@ -58,6 +58,7 @@ internal fun WeekViewPreview( onOpenDay = {}, onCreateAt = { _, _ -> }, onDrop = {}, + onResize = {}, ) } }