diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/domain/EventShift.kt b/app/src/main/java/de/jeanlucmakiola/calendula/domain/EventShift.kt index 07b22449..8ec429a7 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/domain/EventShift.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/domain/EventShift.kt @@ -52,11 +52,7 @@ fun EventForm.shiftedByDays(days: Int, deviceZone: TimeZone): EventForm { return copy(start = newStart, end = (newStart.toInstant(zone) + span).toLocalDateTime(zone)) } -/** - * The form with its start and end set independently — a timeline resize, which - * drags one edge and leaves the other where it stands. Each edge is resolved in - * the form's own zone, as [shiftedTo] does. All-day events are returned untouched. - */ +/** The form with new start and end, each in the form's own zone — a resize. All-day is untouched. */ fun EventForm.resizedTo(newStart: Instant, newEnd: Instant, deviceZone: TimeZone): EventForm { if (isAllDay) return this val zone = resolvedZone(deviceZone) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt index e94078b8..f41d654b 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt @@ -539,8 +539,7 @@ fun CalendarHost( viewModel = eventActions, onEdit = onEditEvent, onDuplicate = { form -> - // As a duplicate from the detail screen: a fresh event, - // through the prefilled-create overlay (#52). + // A fresh event, like a duplicate from the detail screen (#52). importFormSource = ImportSource.Insert importForm = form }, 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 c561b6f5..ca255f7f 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 @@ -82,8 +82,7 @@ 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 } - // Held at the FAB's own height whatever the bar does: the bar is taller, - // and a row grown to fit it moved the FAB up as the bar unfolded. + // Held at the FAB's height so the taller bar can't shift the FAB. Row( modifier = Modifier.height(FAB_SIZE), verticalAlignment = Alignment.CenterVertically, @@ -121,11 +120,7 @@ fun CalendarFabColumn( } } -/** - * The FAB's glyph: "+", or with an event selected a chevron pointing where the - * bar unfolds — turned half round once it has, to fold it back. The "+" and the - * chevron spin past each other rather than fading. - */ +/** "+", or with an event selected a chevron that turns to open and fold the bar. */ @Composable private fun FabGlyph(selecting: Boolean, expanded: Boolean) { val spatial = MaterialTheme.motionScheme.fastSpatialSpec() diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventActionsViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventActionsViewModel.kt index 933c8fb4..f05a5e76 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventActionsViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventActionsViewModel.kt @@ -35,11 +35,7 @@ data class DeletePrompt(val event: EventInstance, val isRecurring: Boolean) /** How an action on a selected event ended, when it has something to say. */ enum class EventActionFailure { LoadFailed, ShareFailed, DeleteFailed, WriteDenied, Gone } -/** - * The event actions a selected block offers outside the detail screen (#141): - * duplicate, share and delete, each loading the event on demand. Hosted at - * `CalendarHost` beside [RescheduleViewModel]. - */ +/** Duplicate, share and delete for a selected event (#141), loading it on demand. */ @HiltViewModel class EventActionsViewModel @Inject constructor( private val repository: CalendarRepository, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventDrag.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventDrag.kt index 1e1efa8d..ee282d4f 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventDrag.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventDrag.kt @@ -116,8 +116,7 @@ private fun rememberPickUpSurface( val currentDrop by rememberUpdatedState(onDrop) val currentCancel by rememberUpdatedState(onCancel) val currentRelease by rememberUpdatedState(onRelease) - // Read per gesture rather than keyed on: the pickup itself can change it, - // and a restart would cancel the drag it just began. + // Not a key: the pickup changes it, and a restart would cancel the drag. val currentImmediate by rememberUpdatedState(immediate) return Modifier @@ -128,9 +127,7 @@ private fun rememberPickUpSurface( val down = awaitFirstDown(requireUnconsumed = false) val origin = down.position val immediate = currentImmediate - // Null on the timeout — held still — true once an [immediate] - // block has been moved past slop, false when the press is - // abandoned. + // Null: held still. True: an immediate pickup past slop. False: abandoned. val outcome = withTimeoutOrNull(holdMillis) { var result: Boolean? = null while (result == null) { 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 c3c3ae0b..71c2bec7 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 @@ -80,8 +80,7 @@ fun EventMoveHost( val resizedLabel = moved?.endMillis?.let { end -> "$movedLabel–${formatTime(end, use24Hour, locale)}" } - // One chip for every message down here: a failed selection action (#141) - // shows when no move has anything to say, rather than stacking over it. + // A failed selection action (#141) shares the chip rather than stacking on it. val message = when (outcome) { null -> actionFailure is MoveOutcome.Moved -> if (resizedLabel != null) { 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 86d58fa2..8568030d 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 @@ -16,12 +16,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import de.jeanlucmakiola.calendula.R -/** - * The action bar a selected event unrolls from the FAB (#141), in the FAB's own - * colours so it stands clear of the calendar behind it. Edit and Delete are - * offered only where the event's calendar allows them; a read-only event can - * still be duplicated into a writable calendar, or shared. - */ +/** The selected event's action bar (#141); Edit and Delete only where its calendar allows. */ @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun EventSelectionBar( 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 78e37fe2..2acb5095 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 @@ -20,11 +20,7 @@ import kotlinx.coroutines.launch /** How far the bottom chips rise while the selection bar is open, to clear it. */ val SelectionBarClearance = 64.dp + SnackChipMargin -/** - * What the FAB becomes while an event is selected (#141): the toggle for its - * action bar, folded while it would cover the selected event's bottom handle. - * Null with nothing selected. - */ +/** The FAB's role while an event is selected (#141): its action bar's toggle. Null otherwise. */ @Composable fun rememberFabSelection( host: ChromeHost, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ResizeHandle.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ResizeHandle.kt index 42cdc40b..429d81c5 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ResizeHandle.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ResizeHandle.kt @@ -29,13 +29,8 @@ private val HANDLE_DOT = 16.dp private val HANDLE_RING = 2.dp /** - * A grip on one edge of a selected event (#141). It takes the gesture from the - * first movement, with no hold — the event is already selected — and hands the - * finger on in root coordinates. A tap is swallowed rather than falling through - * to the slot under it. While not [enabled] it takes no new touch. - * - * No semantics of its own: TalkBack reaches the same change through the event's - * edit action. + * A grip on one edge of a selected event (#141), dragged without a hold. No + * semantics: TalkBack resizes through the event's edit action. */ @Composable fun ResizeHandle( @@ -52,8 +47,7 @@ fun ResizeHandle( val currentMove by rememberUpdatedState(onMove) val currentEnd by rememberUpdatedState(onEnd) val currentCancel by rememberUpdatedState(onCancel) - // Read per gesture: a hidden handle takes no new touch, but turning it off - // mid-resize must not cancel the one it is running. + // Not a key: turning it off mid-resize must not cancel that resize. val currentEnabled by rememberUpdatedState(enabled) Box( contentAlignment = Alignment.Center, 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 57796df0..ea3ec1fc 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 @@ -275,10 +275,8 @@ enum class ResizeEdge { Top, Bottom } data class TimelineResizeDrop(val event: EventInstance, val start: Instant, val end: Instant) /** - * The span after dragging [edge] to [edgeMin] in day [edgeDay], all in minutes - * from the start day's midnight. - * The edge snaps to the grid, and never comes closer than one snap step to the - * other end — an event can't be resized away to nothing, or turned inside out. + * The span after dragging [edge] to [edgeMin] in day [edgeDay], in minutes from the + * start day's midnight. Snapped, and at least one snap step long. */ internal fun resizedSpan( edge: ResizeEdge, @@ -287,9 +285,7 @@ internal fun resizedSpan( edgeMin: Float, edgeDay: Int? = null, ): Pair { - // Dragged into a day past the other end — the start into a day after the - // event ends, the end into one before it starts: no change, rather than an - // event collapsed against its other end. + // An edge dragged into a day past the other end changes nothing. if (edgeDay != null) { val endDay = (endMin - 1).floorDiv(MINUTES_PER_DAY).coerceAtLeast(0) val wrongWay = when (edge) { @@ -305,11 +301,7 @@ internal fun resizedSpan( } } -/** - * [minutes] snapped to the grid, kept at [held] until the finger is three - * quarters of a step away from it: plain rounding flips between two slots - * whenever the finger rests near the line between them. - */ +/** [minutes] snapped, kept at [held] until three quarters of a step away. */ internal fun stickySnap(minutes: Float, held: Int?): Int { if (held != null && kotlin.math.abs(minutes - held) < DRAG_SNAP_MINUTES * 0.75f) return held return (minutes / DRAG_SNAP_MINUTES).roundToInt() * DRAG_SNAP_MINUTES @@ -318,12 +310,7 @@ internal fun stickySnap(minutes: Float, held: Int?): Int { /** How far past its column's side the finger has to go before a resize changes day. */ private const val COLUMN_HYSTERESIS = 0.25f -/** - * The column under a finger at [position] columns from the grid's start, kept at - * [held] until the finger is a [COLUMN_HYSTERESIS] into the next. A resize drags - * up and down: the finger drifts sideways as it goes, and in a narrow week column - * every drift over a border moved the edge by a whole day and back. - */ +/** The column under [position], kept at [held] until [COLUMN_HYSTERESIS] into another. */ internal fun stickyColumn(position: Float, held: Int?, lastIndex: Int): Int { val under = position.toInt().coerceIn(0, lastIndex) if (held == null) return under @@ -433,13 +420,8 @@ class TimelineDragController { private set /** - * 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 — 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. + * Whether [block] is the selected event: by instance, by series and start, and + * just after a move by title and times, since a moved occurrence gets new ids. */ fun isSelected(block: TimedBlock): Boolean { val current = selection ?: return false @@ -457,11 +439,7 @@ class TimelineDragController { ) } - /** - * 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. - */ + /** 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) { @@ -539,8 +517,7 @@ class TimelineDragController { pointerInRoot: Offset, blockInRoot: Offset, ) { - // Picking up another event ends the selection; the selected one keeps - // it through the drag, and the screen moves it along with the drop. + // The selected event keeps its selection through a drag; any other ends it. if (!isSelected(block)) deselect() source = block this.clipOffsetMin = clipOffsetMin @@ -658,8 +635,7 @@ class TimelineDragController { resizeEdge = edge resizeDay = start.date resizeStartMin = start.time.toSecondOfDay() / 60 - // Wall clock at both ends, as the drop resolves them: across a DST - // change the event's length in minutes isn't its span on the clock. + // Wall clock at both ends, as the drop resolves them (DST-safe). resizeEndMin = maxOf( resizeStartMin, (end.date.toEpochDays() - start.date.toEpochDays()).toInt() * MINUTES_PER_DAY + @@ -767,11 +743,7 @@ class TimelineDragController { ) } - /** - * Note where the selected block's bottom edge, [extraPx] of handle included, - * sits against the action bar [clearancePx] tall over the bottom of the - * viewport. Under it, the bar draws itself compact out of the way (#141). - */ + /** Note whether the action bar would cover the selected block's bottom handle (#141). */ fun noteSelectedBottom(block: LayoutCoordinates, extraPx: Float, clearancePx: Float) { val viewport = geometry.viewport?.takeIf { it.isAttached } ?: return if (!block.isAttached) return @@ -938,12 +910,7 @@ fun TimedBlock.continuesAfter(day: LocalDate, zone: TimeZone): Boolean = fun TimelineDrop.startInstant(zone: TimeZone): Instant = wallClockInstant(date, startMin - clipOffsetMin, zone) -/** - * [minutes] past [date]'s midnight as an instant, read as wall clock in [zone]. - * Resolved as a day plus a minute of it rather than as an offset from an - * instant, which would shift by an hour across a DST boundary; a block can be - * dragged above its midnight and past the next one, so either end may overflow. - */ +/** [minutes] past [date]'s midnight, either way past it, as wall clock in [zone] (DST-safe). */ fun wallClockInstant(date: LocalDate, minutes: Int, zone: TimeZone): Instant { val day = date.plus(minutes.floorDiv(MINUTES_PER_DAY), DateTimeUnit.DAY) val minute = minutes.mod(MINUTES_PER_DAY) @@ -1207,11 +1174,7 @@ private fun DragCopy( } } -/** - * How far a selected block is lifted, between 0 and 1 (#141). [snap] takes the - * lift over at once — from the floating copy it replaces — rather than raising - * the block a second time. - */ +/** How far a selected block is lifted, 0 to 1 (#141); [snap] takes it over at once. */ @Composable fun selectionLift(selected: Boolean, snap: Boolean = false): Float { val reduceMotion = rememberReduceMotion() 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 61895a3d..4a50a327 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 @@ -563,8 +563,7 @@ private fun WeekContent( ) } - // A long-pressed event stays selected (#141) until something else is touched: - // back, another page, a view switch, or any tap. + // Selection (#141) ends on back, a page or view change, or any tap. val selection = dragController.selection PublishSelection( event = selection?.event, @@ -1562,9 +1561,7 @@ private fun EventBlock( val clipOffset = remember(block, date, zone) { block.clipOffsetMinutes(date, zone) } val shape = remember(cuts) { timedBlockShape(cuts.top, cuts.bottom) } val topInsetPx = with(density) { topInset.toPx() } - // A long press lifts the block (#141): moved, it drags; let go where it was - // picked up, it stays selected. A block that can't move is selected on the - // spot. Once selected, it drags without another hold. + // Long press (#141): move to drag, let go to select; selected blocks drag at once. val dragModifier = rememberEventDragSource( enabled = selectable, key = block.event.instanceId, @@ -1594,25 +1591,20 @@ private fun EventBlock( onRelease = { dragController.select(block.event, fromLift = draggable) }, ) val lifted = draggable && dragController.ghosts(block) - // Let go of without moving, the block takes the floating copy's place as it - // stands: whole and lifted at once, not faded back up and raised again. + // Released in place, the block takes over the copy's lift without a dip. val takesOverLift = selected && dragController.selection?.fromLift == true val ghostFade = ghostAlpha(lifted) val ghost = if (takesOverLift && !lifted) 1f else ghostFade val raise = selectionLift(selected, snap = takesOverLift) - // Where the selected event's bottom handle sits against the action bar, which - // compacts itself rather than cover it. Read off the piece that ends the event. + // Tells the action bar to fold when it would cover the bottom handle. val revealExtraPx = with(density) { (ResizeHandleTarget / 2).toPx() } val barClearancePx = with(density) { SelectionBarClearance.toPx() } val tracksBar = selected && !cuts.bottom - // Not while the block is waiting on its own write: until then it still - // shows the times from before it. + // Not while its own write is pending: it still shows the old times. 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. + // Kept composed while hidden: removing them would cancel their own resize. DisposableEffect(selected) { if (selected) dragController.noteSelectedBlock(true) onDispose { if (selected) dragController.noteSelectedBlock(false) } diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/RescheduleViewModelTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/RescheduleViewModelTest.kt index 27731ed1..ef1fa46e 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/RescheduleViewModelTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/common/RescheduleViewModelTest.kt @@ -680,8 +680,7 @@ class RescheduleViewModelTest { ) advanceUntilIdle() - // The start changed day but not by a whole number of days, so the - // series anchor can't follow it. + // Not a whole-day shift, so the series anchor can't follow. assertThat(vm.scopePrompt.value) .isEqualTo(MoveScopePrompt(occurrenceOnly = true, resize = true)) }