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.
This commit is contained in:
2026-08-11 16:51:03 +02:00
parent 93b9aa8f34
commit 4fca1e5530
7 changed files with 60 additions and 5 deletions

View File

@@ -301,6 +301,19 @@ class SettingsPrefs @Inject constructor(
store.edit { it[TODAY_BUTTON_IN_TOOLBAR_KEY] = enabled } 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<Boolean> = 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 * How far ahead the in-app Agenda screen shows events (v2.11). Defaults to
* [AgendaRange.Month] — a month of upcoming events. Independent of the * [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 MONTH_VIEW_STYLE_KEY = stringPreferencesKey("month_view_style")
internal val TIMELINE_SCALE_KEY = stringPreferencesKey("timeline_scale") internal val TIMELINE_SCALE_KEY = stringPreferencesKey("timeline_scale")
internal val TODAY_BUTTON_IN_TOOLBAR_KEY = booleanPreferencesKey("today_button_in_toolbar") 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 DEFAULT_VIEW_KEY = stringPreferencesKey("default_view")
internal val QUICK_SWITCH_VIEWS_KEY = stringPreferencesKey("quick_switch_views") internal val QUICK_SWITCH_VIEWS_KEY = stringPreferencesKey("quick_switch_views")
internal val DRAWER_VIEW_ORDER_KEY = stringPreferencesKey("drawer_view_order") internal val DRAWER_VIEW_ORDER_KEY = stringPreferencesKey("drawer_view_order")

View File

@@ -312,6 +312,9 @@ fun CalendarHost(
heldEditKey = key heldEditKey = key
editKey = 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) { val moveScope = remember(movableCalendarIds, reschedule) {
EventMoveScope( EventMoveScope(
movableCalendarIds = movableCalendarIds, movableCalendarIds = movableCalendarIds,
@@ -339,7 +342,7 @@ fun CalendarHost(
// navigation, so it fades through rather than sliding — paging *within* a // navigation, so it fades through rather than sliding — paging *within* a
// view keeps the directional slide. AnimatedContent keyed on the view type. // view keeps the directional slide. AnimatedContent keyed on the view type.
val viewSwitch = fadeThrough() val viewSwitch = fadeThrough()
CompositionLocalProvider(LocalEventMove provides moveScope) { CompositionLocalProvider(LocalEventMove provides moveScope.takeIf { dragToReschedule }) {
AnimatedContent( AnimatedContent(
targetState = view, targetState = view,
transitionSpec = { viewSwitch }, transitionSpec = { viewSwitch },

View File

@@ -53,4 +53,12 @@ class CalendarHostViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L), started = SharingStarted.WhileSubscribed(5_000L),
initialValue = false, initialValue = false,
) )
/** Whether events may be dragged to another slot to reschedule them (#68, #173). */
val dragToReschedule: StateFlow<Boolean> = prefs.dragToReschedule
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000L),
initialValue = true,
)
} }

View File

@@ -42,6 +42,8 @@ data class SettingsUiState(
val showWeekNumbers: Boolean = false, val showWeekNumbers: Boolean = false,
/** Whether the jump-to-today control sits in the top bar instead of the FAB (#60). */ /** Whether the jump-to-today control sits in the top bar instead of the FAB (#60). */
val todayButtonInToolbar: Boolean = false, 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). */ /** How far ahead the in-app Agenda screen shows events (v2.11). */
val agendaScreenRange: AgendaRange = AgendaRange.Month, val agendaScreenRange: AgendaRange = AgendaRange.Month,
/** How far ahead the agenda widget shows events (v2.11). */ /** How far ahead the agenda widget shows events (v2.11). */

View File

@@ -145,9 +145,15 @@ class SettingsViewModel @Inject constructor(
prefs.showWeekNumbers, prefs.showWeekNumbers,
prefs.agendaShowToday, prefs.agendaShowToday,
prefs.softenCalendarColors, prefs.softenCalendarColors,
prefs.todayButtonInToolbar, combine(
) { hourLines, weekNumbers, showToday, soften, todayInToolbar -> prefs.todayButtonInToolbar,
DisplayToggles(hourLines, weekNumbers, showToday, soften, todayInToolbar) prefs.dragToReschedule,
::Pair,
),
) { hourLines, weekNumbers, showToday, soften, (todayInToolbar, dragToMove) ->
DisplayToggles(
hourLines, weekNumbers, showToday, soften, todayInToolbar, dragToMove,
)
}, },
) { view, screenRange, widgetRange, timeFormat, toggles -> ) { view, screenRange, widgetRange, timeFormat, toggles ->
ViewSettings( ViewSettings(
@@ -157,6 +163,7 @@ class SettingsViewModel @Inject constructor(
agendaShowToday = toggles.agendaShowToday, agendaShowToday = toggles.agendaShowToday,
softenColors = toggles.softenColors, softenColors = toggles.softenColors,
todayButtonInToolbar = toggles.todayButtonInToolbar, todayButtonInToolbar = toggles.todayButtonInToolbar,
dragToReschedule = toggles.dragToReschedule,
) )
}, },
combine( combine(
@@ -189,6 +196,7 @@ class SettingsViewModel @Inject constructor(
agendaShowToday = views.agendaShowToday, agendaShowToday = views.agendaShowToday,
softenColors = views.softenColors, softenColors = views.softenColors,
todayButtonInToolbar = views.todayButtonInToolbar, todayButtonInToolbar = views.todayButtonInToolbar,
dragToReschedule = views.dragToReschedule,
agendaShowRangeBar = misc.showRangeBar, agendaShowRangeBar = misc.showRangeBar,
autofocusEventTitle = misc.autofocusEventTitle, autofocusEventTitle = misc.autofocusEventTitle,
pastEventDisplay = misc.pastEventDisplay, pastEventDisplay = misc.pastEventDisplay,
@@ -289,6 +297,7 @@ class SettingsViewModel @Inject constructor(
val agendaShowToday: Boolean, val agendaShowToday: Boolean,
val softenColors: Boolean, val softenColors: Boolean,
val todayButtonInToolbar: Boolean, val todayButtonInToolbar: Boolean,
val dragToReschedule: Boolean,
) )
private data class DisplayToggles( private data class DisplayToggles(
@@ -297,6 +306,7 @@ class SettingsViewModel @Inject constructor(
val agendaShowToday: Boolean, val agendaShowToday: Boolean,
val softenColors: Boolean, val softenColors: Boolean,
val todayButtonInToolbar: Boolean, val todayButtonInToolbar: Boolean,
val dragToReschedule: Boolean,
) )
private data class MiscSettings( private data class MiscSettings(
@@ -534,6 +544,10 @@ class SettingsViewModel @Inject constructor(
viewModelScope.launch { prefs.setTodayButtonInToolbar(enabled) } 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 * 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 * on what the component state reports; the writes are binder round-trips and

View File

@@ -114,7 +114,7 @@ internal fun ViewsScreen(
GroupedRow( GroupedRow(
title = stringResource(R.string.settings_dim_completed), title = stringResource(R.string.settings_dim_completed),
summary = stringResource(R.string.settings_dim_completed_summary), summary = stringResource(R.string.settings_dim_completed_summary),
position = Position.Bottom, position = Position.Middle,
trailing = { trailing = {
Switch( Switch(
checked = state.dimCompletedEvents, checked = state.dimCompletedEvents,
@@ -123,6 +123,18 @@ internal fun ViewsScreen(
}, },
onClick = { viewModel.setDimCompletedEvents(!state.dimCompletedEvents) }, 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)) Spacer(Modifier.height(8.dp))
SectionHeader(stringResource(R.string.settings_month_header)) SectionHeader(stringResource(R.string.settings_month_header))

View File

@@ -439,6 +439,8 @@
<string name="timeline_scale_custom_summary">The height you pinched the timeline to</string> <string name="timeline_scale_custom_summary">The height you pinched the timeline to</string>
<string name="settings_dim_completed">Dim completed events</string> <string name="settings_dim_completed">Dim completed events</string>
<string name="settings_dim_completed_summary">Fade events that have already ended in month and week view</string> <string name="settings_dim_completed_summary">Fade events that have already ended in month and week view</string>
<string name="settings_drag_to_reschedule">Drag to reschedule</string>
<string name="settings_drag_to_reschedule_summary">Move an event by dragging it to another day or time</string>
<string name="settings_past_events">Past events</string> <string name="settings_past_events">Past events</string>
<string name="settings_past_events_show">Show</string> <string name="settings_past_events_show">Show</string>
<string name="settings_past_events_dim">Dim</string> <string name="settings_past_events_dim">Dim</string>