feat: add ScheduleCache with multiplatform-settings for offline caching
This commit is contained in:
@@ -36,6 +36,7 @@ kotlin {
|
||||
implementation(libs.ktor.serialization.kotlinx.json)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.kotlinx.datetime)
|
||||
implementation(libs.multiplatform.settings)
|
||||
implementation(libs.multiplatform.settings.no.arg)
|
||||
implementation(libs.lifecycle.viewmodel.compose)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package ch.parano.myice.cache
|
||||
|
||||
import ch.parano.myice.models.Event
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
class ScheduleCache(private val settings: Settings) {
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
private val eventListSerializer = ListSerializer(Event.serializer())
|
||||
|
||||
private fun eventsKey(account: String) = "cached_events_$account"
|
||||
private fun agegroupKey(account: String) = "filters_agegroup_$account"
|
||||
private fun subgroupKey(account: String) = "filters_subgroup_$account"
|
||||
private fun typeFilterKey(account: String) = "filters_typefilter_$account"
|
||||
private fun timestampKey(account: String) = "cached_events_timestamp_$account"
|
||||
|
||||
fun saveEvents(account: String, events: List<Event>) {
|
||||
settings.putString(eventsKey(account), json.encodeToString(eventListSerializer, events))
|
||||
settings.putLong(timestampKey(account), Clock.System.now().toEpochMilliseconds())
|
||||
}
|
||||
|
||||
fun loadEvents(account: String): List<Event>? {
|
||||
val jsonString = settings.getStringOrNull(eventsKey(account)) ?: return null
|
||||
return try {
|
||||
json.decodeFromString(eventListSerializer, jsonString)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getLastUpdated(account: String): Long? {
|
||||
return settings.getLongOrNull(timestampKey(account))
|
||||
}
|
||||
|
||||
fun saveFilters(account: String, agegroup: String?, subgroup: String?) {
|
||||
if (agegroup != null) settings.putString(agegroupKey(account), agegroup)
|
||||
else settings.remove(agegroupKey(account))
|
||||
if (subgroup != null) settings.putString(subgroupKey(account), subgroup)
|
||||
else settings.remove(subgroupKey(account))
|
||||
}
|
||||
|
||||
fun loadFilters(account: String): Pair<String?, String?> {
|
||||
return settings.getStringOrNull(agegroupKey(account)) to
|
||||
settings.getStringOrNull(subgroupKey(account))
|
||||
}
|
||||
|
||||
fun saveTypeFilter(account: String, typeFilter: String) {
|
||||
settings.putString(typeFilterKey(account), typeFilter)
|
||||
}
|
||||
|
||||
fun loadTypeFilter(account: String): String? {
|
||||
return settings.getStringOrNull(typeFilterKey(account))
|
||||
}
|
||||
|
||||
fun clearCache(account: String) {
|
||||
settings.remove(eventsKey(account))
|
||||
settings.remove(timestampKey(account))
|
||||
settings.remove(agegroupKey(account))
|
||||
settings.remove(subgroupKey(account))
|
||||
settings.remove(typeFilterKey(account))
|
||||
}
|
||||
|
||||
fun clearAllCache() {
|
||||
val keys = settings.keys.filter { key ->
|
||||
key.startsWith("cached_events_") ||
|
||||
key.startsWith("filters_") ||
|
||||
key.startsWith("cached_events_timestamp_")
|
||||
}
|
||||
keys.forEach { settings.remove(it) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package ch.parano.myice.cache
|
||||
|
||||
import ch.parano.myice.models.Event
|
||||
import com.russhwolf.settings.MapSettings
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ScheduleCacheTest {
|
||||
|
||||
private fun createCache(): Pair<ScheduleCache, Settings> {
|
||||
val settings = MapSettings()
|
||||
return ScheduleCache(settings) to settings
|
||||
}
|
||||
|
||||
private fun sampleEvent(id: String, eventType: String = "Jeu") = Event(
|
||||
idEvent = id,
|
||||
agegroup = "U13 (Elite)",
|
||||
name = "U13 Elite - HC Ajoie",
|
||||
title = "U13 (Elite)\nGame\nArena",
|
||||
opponent = "HC Ajoie",
|
||||
place = "Arena",
|
||||
start = "2024-11-10T14:00:00",
|
||||
end = "2024-11-10T16:15:00",
|
||||
color = "#e4222e",
|
||||
eventType = eventType,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testSaveAndLoadEvents() {
|
||||
val (cache, _) = createCache()
|
||||
val events = listOf(sampleEvent("1"), sampleEvent("2"))
|
||||
cache.saveEvents("default", events)
|
||||
|
||||
val loaded = cache.loadEvents("default")
|
||||
assertNotNull(loaded)
|
||||
assertEquals(2, loaded.size)
|
||||
assertEquals("1", loaded[0].idEvent)
|
||||
assertEquals("2", loaded[1].idEvent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadEventsReturnsNullWhenEmpty() {
|
||||
val (cache, _) = createCache()
|
||||
assertNull(cache.loadEvents("default"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetLastUpdated() {
|
||||
val (cache, _) = createCache()
|
||||
assertNull(cache.getLastUpdated("default"))
|
||||
|
||||
cache.saveEvents("default", listOf(sampleEvent("1")))
|
||||
val timestamp = cache.getLastUpdated("default")
|
||||
assertNotNull(timestamp)
|
||||
assertTrue(timestamp > 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSaveAndLoadFilters() {
|
||||
val (cache, _) = createCache()
|
||||
cache.saveFilters("default", agegroup = "U13 (Elite)", subgroup = "U13 Elite")
|
||||
|
||||
val (agegroup, subgroup) = cache.loadFilters("default")
|
||||
assertEquals("U13 (Elite)", agegroup)
|
||||
assertEquals("U13 Elite", subgroup)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSaveFiltersWithNulls() {
|
||||
val (cache, _) = createCache()
|
||||
cache.saveFilters("default", agegroup = "U13", subgroup = "Elite")
|
||||
cache.saveFilters("default", agegroup = null, subgroup = null)
|
||||
|
||||
val (agegroup, subgroup) = cache.loadFilters("default")
|
||||
assertNull(agegroup)
|
||||
assertNull(subgroup)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClearCache() {
|
||||
val (cache, _) = createCache()
|
||||
cache.saveEvents("default", listOf(sampleEvent("1")))
|
||||
cache.saveFilters("default", "U13", "Elite")
|
||||
|
||||
cache.clearCache("default")
|
||||
|
||||
assertNull(cache.loadEvents("default"))
|
||||
val (agegroup, subgroup) = cache.loadFilters("default")
|
||||
assertNull(agegroup)
|
||||
assertNull(subgroup)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClearAllCache() {
|
||||
val (cache, _) = createCache()
|
||||
cache.saveEvents("default", listOf(sampleEvent("1")))
|
||||
cache.saveEvents("leonard", listOf(sampleEvent("2")))
|
||||
|
||||
cache.clearAllCache()
|
||||
|
||||
assertNull(cache.loadEvents("default"))
|
||||
assertNull(cache.loadEvents("leonard"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTypeFilterPersistence() {
|
||||
val (cache, _) = createCache()
|
||||
cache.saveTypeFilter("default", "GAMES")
|
||||
assertEquals("GAMES", cache.loadTypeFilter("default"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTypeFilterNullWhenNotSet() {
|
||||
val (cache, _) = createCache()
|
||||
assertNull(cache.loadTypeFilter("default"))
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ agp = "8.13.2"
|
||||
ktor = "3.5.1"
|
||||
kotlinx-serialization-json = "1.11.0"
|
||||
kotlinx-coroutines = "1.11.0"
|
||||
kotlinx-datetime = "0.7.0"
|
||||
multiplatform-settings = "1.3.0"
|
||||
lifecycle-viewmodel-compose = "2.10.0"
|
||||
androidx-activity-compose = "1.13.0"
|
||||
@@ -19,6 +20,7 @@ ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "kto
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
|
||||
multiplatform-settings = { module = "com.russhwolf:multiplatform-settings", version.ref = "multiplatform-settings" }
|
||||
multiplatform-settings-no-arg = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "multiplatform-settings" }
|
||||
multiplatform-settings-test = { module = "com.russhwolf:multiplatform-settings-test", version.ref = "multiplatform-settings" }
|
||||
|
||||
Reference in New Issue
Block a user