Merge remote-tracking branch 'origin/release/v2.11.0' into feat/time-format-toggle
# Conflicts: # app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt # app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt # app/src/main/res/values-de/strings.xml # app/src/main/res/values/strings.xml
This commit is contained in:
@@ -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<WeekStartPref> = 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<Long, Int?>.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 = "="
|
||||
|
||||
@@ -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<WeekStartPref> =
|
||||
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(
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -276,8 +276,6 @@
|
||||
<string name="settings_dynamic_color_unavailable">Erfordert Android 12 oder neuer</string>
|
||||
<string name="settings_week_start">Wochenstart</string>
|
||||
<string name="settings_week_start_auto">Automatisch</string>
|
||||
<string name="settings_week_start_monday">Montag</string>
|
||||
<string name="settings_week_start_sunday">Sonntag</string>
|
||||
<string name="settings_time_format">Zeitformat</string>
|
||||
<string name="settings_time_format_auto">Automatisch</string>
|
||||
<string name="settings_time_format_12h">12-Stunden (2:00 PM)</string>
|
||||
|
||||
@@ -269,8 +269,6 @@
|
||||
<string name="settings_dynamic_color_unavailable">Requires Android 12 or newer</string>
|
||||
<string name="settings_week_start">Week starts on</string>
|
||||
<string name="settings_week_start_auto">Automatic</string>
|
||||
<string name="settings_week_start_monday">Monday</string>
|
||||
<string name="settings_week_start_sunday">Sunday</string>
|
||||
<string name="settings_time_format">Time format</string>
|
||||
<string name="settings_time_format_auto">Automatic</string>
|
||||
<string name="settings_time_format_12h">12-hour (2:00 PM)</string>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user