diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9ac45..307237d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.11.0] — 2026-06-27 + +### Added +- Start the week on any day. The **Week starts on** setting (Settings → + Appearance) now offers every weekday — not just Monday or Sunday — alongside + the automatic, locale-based default. Thanks to @zmaherdev for the suggestion + ([#3]). +- Choose a 12- or 24-hour clock. A new **Time format** setting (Settings → + Appearance) lets you force a 12-hour (2:00 PM) or 24-hour (14:00) clock, or + follow the system setting automatically. It applies everywhere times appear — + the week and day timelines, agenda, event details, search and reminders — so + the format is now consistent across the whole app. Thanks to @zmaherdev for + the suggestion ([#6]). +- Optional hour lines in the timeline. A new **Hour lines** switch (Settings → + Appearance) draws a faint separator at each hour in the week and day views, + making it easier to see when events start and end. Off by default. Thanks to + @zmaherdev for the suggestion ([#5]). +- Limit how far ahead the agenda looks. New **Agenda range** and **Agenda widget + range** settings (Settings → Appearance) let the agenda screen and its widget + each show just today, the rest of this week, the rest of this month, a rolling + 7 or 30 days, or a custom number of days. "This week" follows your week-start + preference. Thanks to @zmaherdev for the suggestion ([#4]). + ## [2.10.0] — 2026-06-25 ### Added @@ -627,3 +650,7 @@ automatically, with zero telemetry and no internet permission. [#1]: https://codeberg.org/jlmakiola/calendula/issues/1 [#2]: https://codeberg.org/jlmakiola/calendula/issues/2 +[#3]: https://codeberg.org/jlmakiola/calendula/issues/3 +[#4]: https://codeberg.org/jlmakiola/calendula/issues/4 +[#5]: https://codeberg.org/jlmakiola/calendula/issues/5 +[#6]: https://codeberg.org/jlmakiola/calendula/issues/6 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ad97564..7c9966c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -28,8 +28,8 @@ android { // which builds this version and then creates the matching vX.Y.Z tag + // release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 + // PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md. - versionCode = 21000 - versionName = "2.10.0" + versionCode = 21100 + versionName = "2.11.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } 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 24b12a7..45c6c81 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,8 +19,14 @@ 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 +} /** * Clock convention for time-of-day labels. AUTO follows the device's 24-hour @@ -39,14 +45,13 @@ fun TimeFormatPref.is24Hour(systemIs24Hour: Boolean): Boolean = when (this) { } /** - * 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) } /** @@ -71,7 +76,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) { @@ -83,7 +88,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() } } /** Clock convention for time-of-day labels (v2.11). Defaults to AUTO (system). */ @@ -357,6 +362,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 bf988fd..62cdfa4 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 @@ -106,7 +106,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. */ @@ -492,7 +495,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, @@ -1011,14 +1014,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 timeFormatLabel(pref: TimeFormatPref): String = stringResource( 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 818c509..c4b9baf 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 @@ -18,7 +18,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, /** Clock convention for time labels (v2.11). AUTO follows the system setting. */ val timeFormat: TimeFormatPref = TimeFormatPref.AUTO, /** The calendar view the app opens on, and the home of the view back stack (M1). */ diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1e5a40d..59f3460 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 Zeitformat Automatisch 12-Stunden (2:00 PM) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5c0a913..b83d6f8 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 Time format Automatic 12-hour (2:00 PM) 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) } } diff --git a/fastlane/metadata/android/en-US/changelogs/21100.txt b/fastlane/metadata/android/en-US/changelogs/21100.txt new file mode 100644 index 0000000..7274b4f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/21100.txt @@ -0,0 +1,21 @@ +### Added +- Start the week on any day. The **Week starts on** setting (Settings → + Appearance) now offers every weekday — not just Monday or Sunday — alongside + the automatic, locale-based default. Thanks to @zmaherdev for the suggestion + ([#3]). +- Choose a 12- or 24-hour clock. A new **Time format** setting (Settings → + Appearance) lets you force a 12-hour (2:00 PM) or 24-hour (14:00) clock, or + follow the system setting automatically. It applies everywhere times appear — + the week and day timelines, agenda, event details, search and reminders — so + the format is now consistent across the whole app. Thanks to @zmaherdev for + the suggestion ([#6]). +- Optional hour lines in the timeline. A new **Hour lines** switch (Settings → + Appearance) draws a faint separator at each hour in the week and day views, + making it easier to see when events start and end. Off by default. Thanks to + @zmaherdev for the suggestion ([#5]). +- Limit how far ahead the agenda looks. New **Agenda range** and **Agenda widget + range** settings (Settings → Appearance) let the agenda screen and its widget + each show just today, the rest of this week, the rest of this month, a rolling + 7 or 30 days, or a custom number of days. "This week" follows your week-start + preference. Thanks to @zmaherdev for the suggestion ([#4]). +