From 722fc8725935ac45634c6949cf8ca95b6156f045 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 20 Jul 2026 13:04:57 +0200 Subject: [PATCH] fix(edit): honour week-start pref in recurrence picker + smooth weekday collapse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The weekday toggles now order by the app's "Week starts on" setting (via WeekStartPref.resolveFirstDay), matching the month/week/agenda views, instead of always following the device locale. Threaded through a new EventEditViewModel.firstDayOfWeek flow. - Carry the 16dp section gaps on each block rather than a parent spacedBy, so the weekday card's gap collapses together with the card under AnimatedVisibility — removing the end-of-animation jump in the ends section when switching away from Weekly. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../calendula/ui/edit/EventEditScreen.kt | 27 +++++++++++-------- .../calendula/ui/edit/EventEditViewModel.kt | 17 ++++++++++++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt index 5eeee1f..aa569b0 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt @@ -163,14 +163,12 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.isoDayNumber import kotlinx.datetime.toJavaDayOfWeek import kotlinx.datetime.toJavaLocalDate -import kotlinx.datetime.toKotlinDayOfWeek import kotlinx.datetime.toJavaLocalTime import kotlinx.datetime.toLocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import java.time.format.TextStyle as JavaTextStyle -import java.time.temporal.WeekFields import java.util.Locale import kotlin.time.Clock @@ -502,6 +500,7 @@ private fun EventEditContent( ) { val form = state.form val locale = currentLocale() + val firstDayOfWeek by viewModel.firstDayOfWeek.collectAsStateWithLifecycle() val dark = isSystemInDarkTheme() // The title, date and recurrence are managed by the special-dates sync, so // they're locked here; everything else (reminders, location, notes) is the @@ -1122,6 +1121,7 @@ private fun EventEditContent( RecurrencePickerDialog( current = form.rrule, startDay = form.start.date.dayOfWeek, + firstDayOfWeek = firstDayOfWeek, onSelect = { rrule -> viewModel.setRecurrence(rrule) showRecurrencePicker = false @@ -1293,6 +1293,7 @@ private enum class RecurrenceEndMode { Never, Until, Count } private fun RecurrencePickerDialog( current: String?, startDay: DayOfWeek, + firstDayOfWeek: DayOfWeek, onSelect: (String?) -> Unit, onDismiss: () -> Unit, ) { @@ -1381,10 +1382,10 @@ private fun RecurrencePickerDialog( onClick = { customMode = true }, ) } else { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { + // Section gaps live on each block (not a parent spacedBy) so the + // weekday card's gap collapses *with* it under AnimatedVisibility — + // a parent arrangement would leave a residual gap that snaps shut. + Column(modifier = Modifier.fillMaxWidth()) { // A live, human-readable read-out of the rule being built, so the // effect of the controls below is never a guess. Text( @@ -1414,6 +1415,7 @@ private fun RecurrencePickerDialog( shape = groupedShape(Position.Alone), modifier = Modifier .fillMaxWidth() + .padding(top = 16.dp) .padding(horizontal = 16.dp), ) { Column( @@ -1447,19 +1449,22 @@ private fun RecurrencePickerDialog( } } - // Weekday picks — only meaningful on a weekly rule. + // Weekday picks — only meaningful on a weekly rule. The 16dp gap + // sits inside the animated block so it grows/shrinks with the card. AnimatedVisibility(visible = freq == RecurrenceFreq.Weekly) { Surface( color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = groupedShape(Position.Alone), modifier = Modifier .fillMaxWidth() + .padding(top = 16.dp) .padding(horizontal = 16.dp), ) { WeekdayToggleRow( selected = daysMask.toDaySet(), onToggle = { day -> daysMask = daysMask xor day.toMaskBit() }, locale = locale, + firstDay = firstDayOfWeek, modifier = Modifier.padding(16.dp), ) } @@ -1467,7 +1472,7 @@ private fun RecurrencePickerDialog( // Ends: a connected never / on-a-date / after-N group, the count // field folding into the bottom of the run when chosen. - Column { + Column(modifier = Modifier.padding(top = 16.dp)) { Text( text = stringResource(R.string.event_edit_recurrence_ends), style = MaterialTheme.typography.labelLarge, @@ -1559,11 +1564,11 @@ private fun WeekdayToggleRow( selected: Set, onToggle: (DayOfWeek) -> Unit, locale: Locale, + firstDay: DayOfWeek, modifier: Modifier = Modifier, ) { - val days = remember(locale) { - val first = WeekFields.of(locale).firstDayOfWeek.toKotlinDayOfWeek() - (0 until 7).map { DayOfWeek(((first.isoDayNumber - 1 + it) % 7) + 1) } + val days = remember(firstDay) { + (0 until 7).map { DayOfWeek(((firstDay.isoDayNumber - 1 + it) % 7) + 1) } } Row( horizontalArrangement = Arrangement.SpaceBetween, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt index b339f39..dc4e469 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt @@ -8,7 +8,9 @@ import de.jeanlucmakiola.calendula.data.calendar.NoSuchEventException import de.jeanlucmakiola.calendula.data.di.IoDispatcher import de.jeanlucmakiola.calendula.data.prefs.CalendarPrefs import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs +import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref import de.jeanlucmakiola.calendula.data.prefs.resolveDefaultReminder +import de.jeanlucmakiola.calendula.data.prefs.resolveFirstDay import de.jeanlucmakiola.calendula.domain.AccessLevel import de.jeanlucmakiola.calendula.domain.Availability import de.jeanlucmakiola.calendula.domain.CalendarSource @@ -40,12 +42,14 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant import kotlinx.datetime.toLocalDateTime +import java.util.Locale import kotlin.coroutines.cancellation.CancellationException import kotlin.time.Clock import kotlin.time.Duration.Companion.hours @@ -267,6 +271,19 @@ class EventEditViewModel @Inject constructor( initialValue = null, ) + /** + * First day of the week for ordering the recurrence weekday toggles — the + * app's "week starts on" preference, matching the calendar views (Auto + * falls back to the locale convention). + */ + val firstDayOfWeek: StateFlow = settingsPrefs.weekStart + .map { it.resolveFirstDay(Locale.getDefault()) } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000L), + initialValue = WeekStartPref.Auto.resolveFirstDay(Locale.getDefault()), + ) + /** * Initialise a fresh form for a new event on [date]. [startMinutes] (minutes * from midnight) anchors the start when the form is opened by tapping a slot