diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/CalendarPrefs.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/CalendarPrefs.kt new file mode 100644 index 0000000..8e62f3f --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/CalendarPrefs.kt @@ -0,0 +1,44 @@ +package de.jeanlucmakiola.calendula.data.prefs + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +/** + * App-side preference for "calendars the user has hidden in this app", + * separate from the system's per-calendar VISIBLE flag. + * + * Persisted as a comma-separated string of Long ids; non-numeric tokens are + * silently dropped (defensive — see CalendarPrefsTest). + */ +@Singleton +class CalendarPrefs @Inject constructor( + private val store: DataStore, +) { + + val hiddenCalendarIds: Flow> = store.data.map { prefs -> + prefs[HIDDEN_IDS_KEY].orEmpty() + .split(',') + .mapNotNull { it.trim().toLongOrNull() } + .toSet() + } + + suspend fun setHiddenCalendarIds(ids: Set) { + store.edit { prefs -> + if (ids.isEmpty()) { + prefs.remove(HIDDEN_IDS_KEY) + } else { + prefs[HIDDEN_IDS_KEY] = ids.sorted().joinToString(",") + } + } + } + + companion object { + internal val HIDDEN_IDS_KEY = stringPreferencesKey("hidden_calendar_ids") + } +} diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/CalendarPrefsTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/CalendarPrefsTest.kt new file mode 100644 index 0000000..a72daf3 --- /dev/null +++ b/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/CalendarPrefsTest.kt @@ -0,0 +1,53 @@ +package de.jeanlucmakiola.calendula.data.prefs + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.nio.file.Path + +class CalendarPrefsTest { + + private fun newDataStore(tempDir: Path): DataStore = + PreferenceDataStoreFactory.create( + produceFile = { tempDir.resolve("test_prefs.preferences_pb").toFile() }, + ) + + @Test + fun `hiddenCalendarIds defaults to empty when unset`(@TempDir tempDir: Path) = runTest { + val prefs = CalendarPrefs(newDataStore(tempDir)) + assertThat(prefs.hiddenCalendarIds.first()).isEmpty() + } + + @Test + fun `setHiddenCalendarIds round-trips through DataStore`(@TempDir tempDir: Path) = runTest { + val store = newDataStore(tempDir) + val prefs = CalendarPrefs(store) + prefs.setHiddenCalendarIds(setOf(1L, 42L, 7L)) + assertThat(prefs.hiddenCalendarIds.first()).isEqualTo(setOf(1L, 42L, 7L)) + } + + @Test + fun `setting empty set clears storage`(@TempDir tempDir: Path) = runTest { + val prefs = CalendarPrefs(newDataStore(tempDir)) + prefs.setHiddenCalendarIds(setOf(1L)) + prefs.setHiddenCalendarIds(emptySet()) + assertThat(prefs.hiddenCalendarIds.first()).isEmpty() + } + + @Test + fun `garbage stored string is parsed defensively`(@TempDir tempDir: Path) = runTest { + val store = newDataStore(tempDir) + val prefs = CalendarPrefs(store) + store.updateData { p -> + val mutable = p.toMutablePreferences() + mutable[CalendarPrefs.HIDDEN_IDS_KEY] = "1,abc,3" + mutable + } + assertThat(prefs.hiddenCalendarIds.first()).isEqualTo(setOf(1L, 3L)) + } +}