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 d252ad27..56a3a1ab 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 @@ -77,6 +77,8 @@ class EventSelection( val event: EventInstance, val dismiss: () -> Unit, val create: (() -> Unit)? = null, + /** The event at the times an ongoing move or resize would give it, else null. */ + val preview: () -> EventInstance? = { null }, ) /** The screen on show's [ScreenChrome] and [ScreenActions], for the host to draw. */ @@ -126,10 +128,12 @@ fun PublishSelection( compact: Boolean, onDismiss: () -> Unit, onCreate: (() -> Unit)? = null, + preview: () -> EventInstance? = { null }, ) { val slot = LocalChromeSlot.current ?: return val currentDismiss by rememberUpdatedState(onDismiss) val currentCreate by rememberUpdatedState(onCreate) + val currentPreview by rememberUpdatedState(preview) val drafted = onCreate != null val selection = remember(event, drafted) { event?.let { @@ -137,6 +141,7 @@ fun PublishSelection( event = it, dismiss = { currentDismiss() }, create = if (drafted) ({ currentCreate?.invoke() }) else null, + preview = { currentPreview() }, ) } } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/SelectionTopBar.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/SelectionTopBar.kt index 37ef7592..d9d69b71 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/SelectionTopBar.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/SelectionTopBar.kt @@ -11,6 +11,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -30,10 +32,13 @@ import kotlin.time.toJavaInstant @Composable fun SelectionTopBar(selection: EventSelection) { val event = selection.event + // Follows a move or resize, so the times stay readable under the finger. + val preview by remember(selection) { derivedStateOf { selection.preview() } } + val shown = preview ?: event val use24Hour = LocalUse24HourFormat.current val locale = currentLocale() - val whenText = remember(event.start, event.end, use24Hour, locale) { - formatSelectionWhen(event, use24Hour, locale) + val whenText = remember(shown.start, shown.end, use24Hour, locale) { + formatSelectionWhen(shown, use24Hour, locale) } TopAppBar( navigationIcon = { 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 045d14fa..45bb3b4e 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 @@ -19,6 +19,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableIntStateOf @@ -815,6 +816,25 @@ class TimelineDragController { originSlot = drag?.slot } + /** The slot the gesture would land on, changing once per snap rather than per frame. */ + private val landingSlot by derivedStateOf { drag?.slot } + + /** + * The dragged event at the times letting go now would give it, worked out as + * [finish] and [finishResize] do. Null while it is still on its own slot (#141). + */ + fun preview(zone: TimeZone): EventInstance? { + val (date, startMin, spanMin) = landingSlot?.takeIf { it != originSlot } ?: return null + val event = source?.event ?: return null + val edge = resizeEdge + if (edge != null) { + val drop = resizedDrop(event, edge, date, startMin, spanMin, zone) + return event.copy(start = drop.start, end = drop.end) + } + val start = TimelineDrop(event, date, startMin, clipOffsetMin).startInstant(zone) + return event.copy(start = start, end = start + dragDuration) + } + /** * End a resize, handing back the event's new span — null when it never * resolved or came back to the length it started at. Settles as [finish] does. 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 03873c14..66fe65d5 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 @@ -614,6 +614,7 @@ private fun WeekContent( compact = dragController.selectionCovered, onDismiss = dragController::deselect, onCreate = selection?.event?.takeIf { it.isDraft }?.let { draft -> { createDraft(draft) } }, + preview = { dragController.preview(zone) }, ) BackHandler(enabled = selection != null) { dragController.deselect() } LaunchedEffect(pagerState, dragController) {