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 2b3cb82..8585832 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 @@ -19,18 +19,23 @@ import javax.inject.Singleton /** Light/dark override. SYSTEM follows the device setting. */ enum class ThemeMode { SYSTEM, LIGHT, DARK } -/** Week-start override. AUTO derives the first day from the active locale. */ -enum class WeekStartPref { AUTO, MONDAY, SUNDAY } +/** + * Week-start override. [Auto] derives the first day from the active locale; + * [Day] pins a specific weekday (any of the seven). + */ +sealed interface WeekStartPref { + data object Auto : WeekStartPref + data class Day(val day: DayOfWeek) : WeekStartPref +} /** - * Resolve the preference to a concrete first-day-of-week. AUTO reads the - * locale's convention (e.g. Monday in DE, Sunday in en-US). + * Resolve the preference to a concrete first-day-of-week. [WeekStartPref.Auto] + * reads the locale's convention (e.g. Monday in DE, Sunday in en-US). */ fun WeekStartPref.resolveFirstDay(locale: Locale): DayOfWeek = when (this) { - WeekStartPref.MONDAY -> DayOfWeek.MONDAY - WeekStartPref.SUNDAY -> DayOfWeek.SUNDAY + is WeekStartPref.Day -> day // java.time.DayOfWeek.value is ISO 1..7 (Mon..Sun) — same numbering kotlinx uses. - WeekStartPref.AUTO -> DayOfWeek(WeekFields.of(locale).firstDayOfWeek.value) + WeekStartPref.Auto -> DayOfWeek(WeekFields.of(locale).firstDayOfWeek.value) } /** @@ -55,7 +60,7 @@ class SettingsPrefs @Inject constructor( } val weekStart: Flow = store.data.map { prefs -> - prefs[WEEK_START_KEY].toEnum(WeekStartPref.AUTO) + parseWeekStart(prefs[WEEK_START_KEY]) } suspend fun setThemeMode(mode: ThemeMode) { @@ -67,7 +72,7 @@ class SettingsPrefs @Inject constructor( } suspend fun setWeekStart(pref: WeekStartPref) { - store.edit { it[WEEK_START_KEY] = pref.name } + store.edit { it[WEEK_START_KEY] = pref.storageValue() } } /** @@ -331,6 +336,25 @@ private fun MutableMap.applyOverride( } } +/** Sentinel stored for [WeekStartPref.Auto]; days store their [DayOfWeek.name]. */ +private const val WEEK_START_AUTO = "AUTO" + +private fun WeekStartPref.storageValue(): String = when (this) { + WeekStartPref.Auto -> WEEK_START_AUTO + is WeekStartPref.Day -> day.name +} + +/** + * Parse the stored week-start value. "AUTO"/null/garbage → [WeekStartPref.Auto]; + * a [DayOfWeek] name → [WeekStartPref.Day]. The legacy "MONDAY"/"SUNDAY" enum + * values migrate transparently, since both are valid day names. + */ +private fun parseWeekStart(stored: String?): WeekStartPref = when (stored) { + null, WEEK_START_AUTO -> WeekStartPref.Auto + else -> DayOfWeek.entries.firstOrNull { it.name == stored } + ?.let { WeekStartPref.Day(it) } ?: WeekStartPref.Auto +} + private const val NONE = "none" private const val ENTRY_SEP = ";" private const val KEY_VALUE_SEP = "=" diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt index 21a8f7a..062b7b0 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt @@ -105,7 +105,10 @@ import de.jeanlucmakiola.calendula.ui.common.TimePickerAlert import de.jeanlucmakiola.calendula.ui.common.positionOf import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec +import de.jeanlucmakiola.calendula.ui.common.currentLocale +import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalTime +import java.time.format.TextStyle as JavaTextStyle import java.util.Calendar /** The settings sub-screens reached from the hub's category rows. */ @@ -484,7 +487,7 @@ private fun AppearanceScreen( if (showWeekStart) { OptionPicker( title = stringResource(R.string.settings_week_start), - options = WeekStartPref.entries, + options = WEEK_START_OPTIONS, selected = state.weekStart, label = { weekStartLabel(it) }, onSelect = viewModel::setWeekStart, @@ -993,14 +996,18 @@ private fun themeLabel(mode: ThemeMode): String = stringResource( }, ) +/** Picker options: "Follow system" first, then Monday…Sunday in ISO order. */ +private val WEEK_START_OPTIONS: List = + listOf(WeekStartPref.Auto) + DayOfWeek.entries.map { WeekStartPref.Day(it) } + @Composable -private fun weekStartLabel(pref: WeekStartPref): String = stringResource( - when (pref) { - WeekStartPref.AUTO -> R.string.settings_week_start_auto - WeekStartPref.MONDAY -> R.string.settings_week_start_monday - WeekStartPref.SUNDAY -> R.string.settings_week_start_sunday - }, -) +private fun weekStartLabel(pref: WeekStartPref): String = when (pref) { + WeekStartPref.Auto -> stringResource(R.string.settings_week_start_auto) + // Localised full weekday name, so any of the seven days reads naturally + // without a per-day string resource. + is WeekStartPref.Day -> java.time.DayOfWeek.of(pref.day.ordinal + 1) + .getDisplayName(JavaTextStyle.FULL, currentLocale()) +} @Composable private fun languageLabel(tag: String?): String = 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 1dac3bb..b625722 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 @@ -17,7 +17,7 @@ data class SettingsUiState( val themeMode: ThemeMode = ThemeMode.SYSTEM, val dynamicColor: Boolean = true, val dynamicColorAvailable: Boolean = true, - val weekStart: WeekStartPref = WeekStartPref.AUTO, + val weekStart: WeekStartPref = WeekStartPref.Auto, /** The calendar view the app opens on, and the home of the view back stack (M1). */ val defaultView: CalendarView = CalendarView.Week, /** Optional event-form fields shown by default (rest behind "more fields"). */ diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index aa24a30..b133ec8 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -276,8 +276,6 @@ Erfordert Android 12 oder neuer Wochenstart Automatisch - Montag - Sonntag Termin-Formular Standardmäßig angezeigte Felder — alles Weitere liegt hinter \"Weitere Felder\" Farben auf nicht unterstützten Kalendern erlauben diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 644286f..8851159 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -269,8 +269,6 @@ Requires Android 12 or newer Week starts on Automatic - Monday - Sunday New event form Fields shown by default — everything else sits behind \"More fields\" Allow colors on unsupported calendars diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt index dee5554..7d7d052 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt @@ -25,7 +25,7 @@ class SettingsPrefsTest { val prefs = SettingsPrefs(newDataStore(tempDir)) assertThat(prefs.themeMode.first()).isEqualTo(ThemeMode.SYSTEM) assertThat(prefs.dynamicColor.first()).isTrue() - assertThat(prefs.weekStart.first()).isEqualTo(WeekStartPref.AUTO) + assertThat(prefs.weekStart.first()).isEqualTo(WeekStartPref.Auto) } @Test @@ -43,10 +43,34 @@ class SettingsPrefsTest { } @Test - fun `week start round-trips`(@TempDir tempDir: Path) = runTest { + fun `week start round-trips any day`(@TempDir tempDir: Path) = runTest { val prefs = SettingsPrefs(newDataStore(tempDir)) - prefs.setWeekStart(WeekStartPref.SUNDAY) - assertThat(prefs.weekStart.first()).isEqualTo(WeekStartPref.SUNDAY) + prefs.setWeekStart(WeekStartPref.Day(DayOfWeek.SATURDAY)) + assertThat(prefs.weekStart.first()).isEqualTo(WeekStartPref.Day(DayOfWeek.SATURDAY)) + } + + @Test + fun `legacy MONDAY week-start value migrates to Day`(@TempDir tempDir: Path) = runTest { + val store = newDataStore(tempDir) + val prefs = SettingsPrefs(store) + store.updateData { p -> + val m = p.toMutablePreferences() + m[SettingsPrefs.WEEK_START_KEY] = "MONDAY" + m + } + assertThat(prefs.weekStart.first()).isEqualTo(WeekStartPref.Day(DayOfWeek.MONDAY)) + } + + @Test + fun `garbage week-start value falls back to Auto`(@TempDir tempDir: Path) = runTest { + val store = newDataStore(tempDir) + val prefs = SettingsPrefs(store) + store.updateData { p -> + val m = p.toMutablePreferences() + m[SettingsPrefs.WEEK_START_KEY] = "FUNDAY" + m + } + assertThat(prefs.weekStart.first()).isEqualTo(WeekStartPref.Auto) } @Test @@ -252,13 +276,15 @@ class SettingsPrefsTest { @Test fun `explicit week-start prefs resolve regardless of locale`() { - assertThat(WeekStartPref.MONDAY.resolveFirstDay(Locale.US)).isEqualTo(DayOfWeek.MONDAY) - assertThat(WeekStartPref.SUNDAY.resolveFirstDay(Locale.GERMANY)).isEqualTo(DayOfWeek.SUNDAY) + assertThat(WeekStartPref.Day(DayOfWeek.MONDAY).resolveFirstDay(Locale.US)) + .isEqualTo(DayOfWeek.MONDAY) + assertThat(WeekStartPref.Day(DayOfWeek.SATURDAY).resolveFirstDay(Locale.GERMANY)) + .isEqualTo(DayOfWeek.SATURDAY) } @Test fun `auto week start follows the locale convention`() { - assertThat(WeekStartPref.AUTO.resolveFirstDay(Locale.GERMANY)).isEqualTo(DayOfWeek.MONDAY) - assertThat(WeekStartPref.AUTO.resolveFirstDay(Locale.US)).isEqualTo(DayOfWeek.SUNDAY) + assertThat(WeekStartPref.Auto.resolveFirstDay(Locale.GERMANY)).isEqualTo(DayOfWeek.MONDAY) + assertThat(WeekStartPref.Auto.resolveFirstDay(Locale.US)).isEqualTo(DayOfWeek.SUNDAY) } }