From 4fca1e55300ecfec7f8e9f650ddd025253bdc194 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Tue, 11 Aug 2026 16:51:03 +0200 Subject: [PATCH] Add a setting to turn drag-to-reschedule off (#173) Drag to move is on by default; turning it off drops the move scope, so no event block registers a drag gesture at all. --- .../calendula/data/prefs/SettingsPrefs.kt | 14 +++++++++++++ .../calendula/ui/CalendarHost.kt | 5 ++++- .../calendula/ui/CalendarHostViewModel.kt | 8 ++++++++ .../calendula/ui/settings/SettingsUiState.kt | 2 ++ .../ui/settings/SettingsViewModel.kt | 20 ++++++++++++++++--- .../calendula/ui/settings/ViewsSettings.kt | 14 ++++++++++++- app/src/main/res/values/strings.xml | 2 ++ 7 files changed, 60 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt index 5fa676d..6bc5849 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt @@ -301,6 +301,19 @@ class SettingsPrefs @Inject constructor( store.edit { it[TODAY_BUTTON_IN_TOOLBAR_KEY] = enabled } } + /** + * Whether events can be dragged to another slot in the calendar views (#68, + * #173). Defaults to ON. Off means no block registers a drag gesture at all; + * rescheduling then goes through the edit form. + */ + val dragToReschedule: Flow = store.data.map { prefs -> + prefs[DRAG_TO_RESCHEDULE_KEY] ?: true + } + + suspend fun setDragToReschedule(enabled: Boolean) { + store.edit { it[DRAG_TO_RESCHEDULE_KEY] = enabled } + } + /** * How far ahead the in-app Agenda screen shows events (v2.11). Defaults to * [AgendaRange.Month] — a month of upcoming events. Independent of the @@ -945,6 +958,7 @@ class SettingsPrefs @Inject constructor( internal val MONTH_VIEW_STYLE_KEY = stringPreferencesKey("month_view_style") internal val TIMELINE_SCALE_KEY = stringPreferencesKey("timeline_scale") internal val TODAY_BUTTON_IN_TOOLBAR_KEY = booleanPreferencesKey("today_button_in_toolbar") + internal val DRAG_TO_RESCHEDULE_KEY = booleanPreferencesKey("drag_to_reschedule") internal val DEFAULT_VIEW_KEY = stringPreferencesKey("default_view") internal val QUICK_SWITCH_VIEWS_KEY = stringPreferencesKey("quick_switch_views") internal val DRAWER_VIEW_ORDER_KEY = stringPreferencesKey("drawer_view_order") 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 f2ef6e5..0276be3 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt @@ -312,6 +312,9 @@ fun CalendarHost( heldEditKey = key editKey = key } + // Off by preference (#173) means no scope at all: every block then registers + // no drag gesture, and the edit form stays the way to reschedule. + val dragToReschedule = viewModel.dragToReschedule.collectAsStateWithLifecycle().value val moveScope = remember(movableCalendarIds, reschedule) { EventMoveScope( movableCalendarIds = movableCalendarIds, @@ -339,7 +342,7 @@ fun CalendarHost( // navigation, so it fades through rather than sliding — paging *within* a // view keeps the directional slide. AnimatedContent keyed on the view type. val viewSwitch = fadeThrough() - CompositionLocalProvider(LocalEventMove provides moveScope) { + CompositionLocalProvider(LocalEventMove provides moveScope.takeIf { dragToReschedule }) { AnimatedContent( targetState = view, transitionSpec = { viewSwitch }, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHostViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHostViewModel.kt index a5f3104..a50f1ac 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHostViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHostViewModel.kt @@ -53,4 +53,12 @@ class CalendarHostViewModel @Inject constructor( started = SharingStarted.WhileSubscribed(5_000L), initialValue = false, ) + + /** Whether events may be dragged to another slot to reschedule them (#68, #173). */ + val dragToReschedule: StateFlow = prefs.dragToReschedule + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000L), + initialValue = true, + ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt index c3cf3fc..effeb71 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt @@ -42,6 +42,8 @@ data class SettingsUiState( val showWeekNumbers: Boolean = false, /** Whether the jump-to-today control sits in the top bar instead of the FAB (#60). */ val todayButtonInToolbar: Boolean = false, + /** Whether events can be dragged to another slot to reschedule them (#68, #173). */ + val dragToReschedule: Boolean = true, /** How far ahead the in-app Agenda screen shows events (v2.11). */ val agendaScreenRange: AgendaRange = AgendaRange.Month, /** How far ahead the agenda widget shows events (v2.11). */ diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt index 8b49d6e..d2b37fe 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt @@ -145,9 +145,15 @@ class SettingsViewModel @Inject constructor( prefs.showWeekNumbers, prefs.agendaShowToday, prefs.softenCalendarColors, - prefs.todayButtonInToolbar, - ) { hourLines, weekNumbers, showToday, soften, todayInToolbar -> - DisplayToggles(hourLines, weekNumbers, showToday, soften, todayInToolbar) + combine( + prefs.todayButtonInToolbar, + prefs.dragToReschedule, + ::Pair, + ), + ) { hourLines, weekNumbers, showToday, soften, (todayInToolbar, dragToMove) -> + DisplayToggles( + hourLines, weekNumbers, showToday, soften, todayInToolbar, dragToMove, + ) }, ) { view, screenRange, widgetRange, timeFormat, toggles -> ViewSettings( @@ -157,6 +163,7 @@ class SettingsViewModel @Inject constructor( agendaShowToday = toggles.agendaShowToday, softenColors = toggles.softenColors, todayButtonInToolbar = toggles.todayButtonInToolbar, + dragToReschedule = toggles.dragToReschedule, ) }, combine( @@ -189,6 +196,7 @@ class SettingsViewModel @Inject constructor( agendaShowToday = views.agendaShowToday, softenColors = views.softenColors, todayButtonInToolbar = views.todayButtonInToolbar, + dragToReschedule = views.dragToReschedule, agendaShowRangeBar = misc.showRangeBar, autofocusEventTitle = misc.autofocusEventTitle, pastEventDisplay = misc.pastEventDisplay, @@ -289,6 +297,7 @@ class SettingsViewModel @Inject constructor( val agendaShowToday: Boolean, val softenColors: Boolean, val todayButtonInToolbar: Boolean, + val dragToReschedule: Boolean, ) private data class DisplayToggles( @@ -297,6 +306,7 @@ class SettingsViewModel @Inject constructor( val agendaShowToday: Boolean, val softenColors: Boolean, val todayButtonInToolbar: Boolean, + val dragToReschedule: Boolean, ) private data class MiscSettings( @@ -534,6 +544,10 @@ class SettingsViewModel @Inject constructor( viewModelScope.launch { prefs.setTodayButtonInToolbar(enabled) } } + fun setDragToReschedule(enabled: Boolean) { + viewModelScope.launch { prefs.setDragToReschedule(enabled) } + } + /** * Switch the launcher label (#44). The card highlights at once, then settles * on what the component state reports; the writes are binder round-trips and diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/ViewsSettings.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/ViewsSettings.kt index 18bd68f..51943d9 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/ViewsSettings.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/ViewsSettings.kt @@ -114,7 +114,7 @@ internal fun ViewsScreen( GroupedRow( title = stringResource(R.string.settings_dim_completed), summary = stringResource(R.string.settings_dim_completed_summary), - position = Position.Bottom, + position = Position.Middle, trailing = { Switch( checked = state.dimCompletedEvents, @@ -123,6 +123,18 @@ internal fun ViewsScreen( }, onClick = { viewModel.setDimCompletedEvents(!state.dimCompletedEvents) }, ) + GroupedRow( + title = stringResource(R.string.settings_drag_to_reschedule), + summary = stringResource(R.string.settings_drag_to_reschedule_summary), + position = Position.Bottom, + trailing = { + Switch( + checked = state.dragToReschedule, + onCheckedChange = viewModel::setDragToReschedule, + ) + }, + onClick = { viewModel.setDragToReschedule(!state.dragToReschedule) }, + ) Spacer(Modifier.height(8.dp)) SectionHeader(stringResource(R.string.settings_month_header)) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cd92955..d8c06b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -439,6 +439,8 @@ The height you pinched the timeline to Dim completed events Fade events that have already ended in month and week view + Drag to reschedule + Move an event by dragging it to another day or time Past events Show Dim