core-prefs: typed snapshot access and nullableBooleanPref

This commit is contained in:
2026-10-16 12:55:30 +02:00
parent 5093e1cc5f
commit 3b376ac2dd
4 changed files with 57 additions and 2 deletions
+4
View File
@@ -30,6 +30,10 @@ are no binary releases; "version" tracks the shared `version` in the root build
### Added
- **`core-prefs`** — `Preferences[pref]` and `MutablePreferences[pref] = value`,
so an app can read several prefs out of one `PrefStore.data` snapshot and
write several in one `PrefStore.edit` without dropping to raw keys. Plus
`nullableBooleanPref`, for a tri-state flag where unset differs from `false`.
- **`components`** — picker-row helpers lifted from Calendula's pickers and
settings, all additive. `GroupedRow` takes `checked` (default `false`): it
implies `selected` and fills an empty trailing slot with `SelectedCheck`,
@@ -54,10 +54,20 @@ fun stringSetPref(name: String, default: Set<String> = emptySet()): Pref<Set<Str
fun nullableStringPref(name: String): Pref<String?> =
nullablePref(stringPreferencesKey(name), name)
/** Absent reads back as `null`; writing `null` removes the key. */
fun nullableBooleanPref(name: String): Pref<Boolean?> =
nullablePref(booleanPreferencesKey(name), name)
/** Absent reads back as `null`; writing `null` removes the key. */
fun nullableLongPref(name: String): Pref<Long?> =
nullablePref(longPreferencesKey(name), name)
/** [pref]'s value in this snapshot, for reading several prefs in one `PrefStore.data` map. */
operator fun <T> Preferences.get(pref: Pref<T>): T = pref.read(this)
/** Writes [pref] inside a multi-key `PrefStore.edit`, with the same encoding as `PrefStore.set`. */
operator fun <T> MutablePreferences.set(pref: Pref<T>, value: T) = pref.write(this, value)
/** An absent key reads back as [default]; the value is stored under [key] verbatim. */
private fun <T : Any> scalarPref(key: Preferences.Key<T>, name: String, default: T): Pref<T> =
Pref(
@@ -281,4 +281,45 @@ class PrefStoreTest {
assertThat(store.get(pref)).isFalse()
}
@Test
fun `a nullable boolean keeps false apart from unset`(@TempDir tempDir: Path) = runTest {
val store = newStore(tempDir)
val pref = nullableBooleanPref("b")
assertThat(store.get(pref)).isNull()
store.set(pref, false)
assertThat(store.get(pref)).isFalse()
store.set(pref, null)
assertThat(store.get(pref)).isNull()
}
@Test
fun `several prefs read from one snapshot`(@TempDir tempDir: Path) = runTest {
val store = newStore(tempDir)
val count = intPref("count", 1)
val mode = enumPref("mode", ThemeMode.SYSTEM)
store.set(mode, ThemeMode.DARK)
val snapshot = store.data.first()
assertThat(snapshot[count] to snapshot[mode]).isEqualTo(1 to ThemeMode.DARK)
}
@Test
fun `a multi-key edit writes typed prefs with their codecs`(@TempDir tempDir: Path) = runTest {
val store = newStore(tempDir)
val mode = enumPref("mode", ThemeMode.SYSTEM)
val folder = nullableStringPref("folder")
store.set(folder, "content://x")
store.edit {
it[mode] = ThemeMode.BLACK
it[folder] = null
}
assertThat(store.data.first()[stringPreferencesKey("mode")]).isEqualTo("BLACK")
assertThat(store.get(folder)).isNull()
}
}
@@ -46,11 +46,11 @@ class PrefTest {
@Test
fun `the nullable factories default to null`() {
val prefs = listOf(nullableStringPref("s"), nullableLongPref("l"))
val prefs = listOf(nullableStringPref("s"), nullableLongPref("l"), nullableBooleanPref("b"))
val defaults = prefs.map { it.default }
assertThat(defaults).containsExactly(null, null)
assertThat(defaults).containsExactly(null, null, null)
}
@Test