[1.514.*] Pre-release merge (#1123)
This commit is contained in:
@@ -248,3 +248,4 @@ distribution/
|
||||
screenshots/
|
||||
claw-export.json
|
||||
.kotlin/
|
||||
.worktrees/
|
||||
|
||||
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- The migration path for tag data format changes in 1.61.0 has been removed.
|
||||
|
||||
## [1.63.0] - 2026-04-02
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -99,7 +99,6 @@ dependencies {
|
||||
implementation(libs.androidx.collection)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.core.splashscreen)
|
||||
implementation(libs.androidx.datastore.preferences.core)
|
||||
implementation(libs.androidx.lifecycle.common)
|
||||
implementation(libs.androidx.lifecycle.compose)
|
||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||
|
||||
@@ -27,9 +27,6 @@ dependencies {
|
||||
api(libs.androidx.compose.foundation.layout)
|
||||
api(libs.androidx.compose.ui)
|
||||
api(libs.androidx.compose.ui.graphics)
|
||||
api(libs.androidx.datastore)
|
||||
api(libs.androidx.datastore.core)
|
||||
api(libs.androidx.datastore.preferences.core)
|
||||
api(libs.androidx.lifecycle.viewmodel)
|
||||
api(projects.api)
|
||||
api(projects.core)
|
||||
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright © Harsh Shandilya.
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
package dev.msfjarvis.claw.common.persistence
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.datastore.core.DataMigration
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.PreferencesFileSerializer
|
||||
import androidx.datastore.preferences.core.emptyPreferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.IntoSet
|
||||
import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
|
||||
@BindingContainer
|
||||
@ContributesTo(AppScope::class)
|
||||
object PreferencesStoreModule {
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providePreferencesDataStore(
|
||||
context: Context,
|
||||
migrations: Set<DataMigration<Preferences>>,
|
||||
): DataStore<Preferences> {
|
||||
return DataStoreFactory.create(
|
||||
corruptionHandler =
|
||||
ReplaceFileCorruptionHandler {
|
||||
Log.d(
|
||||
"PreferencesStore",
|
||||
"Preferences data store corruption detected, returning empty preferences.",
|
||||
it,
|
||||
)
|
||||
emptyPreferences()
|
||||
},
|
||||
migrations = migrations.toList(),
|
||||
produceFile = { context.preferencesDataStoreFile("claw_preferences") },
|
||||
serializer = PreferencesFileSerializer,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun firstMigration(): DataMigration<Preferences> =
|
||||
object : DataMigration<Preferences> {
|
||||
override suspend fun shouldMigrate(currentData: Preferences): Boolean = false
|
||||
|
||||
override suspend fun migrate(currentData: Preferences): Preferences = currentData
|
||||
|
||||
override suspend fun cleanUp() {}
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,6 @@
|
||||
*/
|
||||
package dev.msfjarvis.claw.common.tags
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||
import app.cash.sqldelight.coroutines.asFlow
|
||||
import app.cash.sqldelight.coroutines.mapToList
|
||||
import dev.msfjarvis.claw.core.coroutines.DatabaseReadDispatcher
|
||||
@@ -18,77 +13,28 @@ import dev.msfjarvis.claw.core.coroutines.DatabaseWriteDispatcher
|
||||
import dev.msfjarvis.claw.database.local.TagBlocksQueries
|
||||
import dev.msfjarvis.claw.model.TagBlock
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.sentry.Sentry
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@Inject
|
||||
class TagBlockRepository(
|
||||
private val tagBlocksQueries: TagBlocksQueries,
|
||||
private val preferences: DataStore<Preferences>,
|
||||
@param:DatabaseReadDispatcher private val readDispatcher: CoroutineDispatcher,
|
||||
@param:DatabaseWriteDispatcher private val writeDispatcher: CoroutineDispatcher,
|
||||
) {
|
||||
private val legacyTagsKey = stringSetPreferencesKey("tags")
|
||||
private val timedTagsKey = stringPreferencesKey("tags_with_expiration")
|
||||
|
||||
private var migrationCompleted = false
|
||||
|
||||
private suspend fun migrateFromDataStore() {
|
||||
if (migrationCompleted) return
|
||||
|
||||
val prefs = preferences.data.first()
|
||||
val timedTags = prefs[timedTagsKey]
|
||||
val legacyTags = prefs[legacyTagsKey]
|
||||
|
||||
if (timedTags == null && legacyTags == null) {
|
||||
migrationCompleted = true
|
||||
Sentry.metrics().count("tag_sqlite_migration_not_required")
|
||||
return
|
||||
} else {
|
||||
Sentry.metrics().count("tag_sqlite_migration_required")
|
||||
}
|
||||
|
||||
withContext(writeDispatcher) {
|
||||
if (timedTags != null) {
|
||||
val tagMap = Json.decodeFromString<Map<String, Long?>>(timedTags)
|
||||
tagMap.forEach { (tag, expiration) -> tagBlocksQueries.insertOrReplace(tag, expiration) }
|
||||
} else {
|
||||
legacyTags?.forEach { tag -> tagBlocksQueries.insertOrReplace(tag, null) }
|
||||
}
|
||||
|
||||
// Clean up DataStore after successful migration
|
||||
preferences.edit { prefs ->
|
||||
prefs.remove(timedTagsKey)
|
||||
prefs.remove(legacyTagsKey)
|
||||
}
|
||||
|
||||
migrationCompleted = true
|
||||
}
|
||||
}
|
||||
|
||||
fun getSavedTags(): Flow<Set<String>> {
|
||||
val now = System.currentTimeMillis()
|
||||
return tagBlocksQueries
|
||||
.selectActiveTags(now)
|
||||
.asFlow()
|
||||
.mapToList(readDispatcher)
|
||||
.onStart { migrateFromDataStore() }
|
||||
.map { it.toSet() }
|
||||
return tagBlocksQueries.selectActiveTags(now).asFlow().mapToList(readDispatcher).map {
|
||||
it.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
fun getTagBlocks(): Flow<List<TagBlock>> {
|
||||
return tagBlocksQueries
|
||||
.selectAll()
|
||||
.asFlow()
|
||||
.mapToList(readDispatcher)
|
||||
.onStart { migrateFromDataStore() }
|
||||
.map { blocks -> blocks.map { TagBlock(it.tag, it.expiration_millis) } }
|
||||
return tagBlocksQueries.selectAll().asFlow().mapToList(readDispatcher).map { blocks ->
|
||||
blocks.map { TagBlock(it.tag, it.expiration_millis) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveTagBlock(tag: String, expirationMillis: Long?) {
|
||||
|
||||
@@ -6,74 +6,59 @@
|
||||
*/
|
||||
package dev.msfjarvis.claw.common.tags
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import dev.msfjarvis.claw.database.LobstersDatabase
|
||||
import dev.msfjarvis.claw.database.local.TagBlocksQueries
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class TagBlockRepositoryTest {
|
||||
@TempDir lateinit var tempDir: File
|
||||
|
||||
private lateinit var driver: JdbcSqliteDriver
|
||||
private lateinit var tagBlocksQueries: TagBlocksQueries
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
|
||||
|
||||
driver.execute(
|
||||
null,
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS TagBlocks (
|
||||
tag TEXT PRIMARY KEY NOT NULL,
|
||||
expiration_millis INTEGER
|
||||
)
|
||||
"""
|
||||
.trimIndent(),
|
||||
0,
|
||||
)
|
||||
|
||||
driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
|
||||
LobstersDatabase.Schema.create(driver)
|
||||
tagBlocksQueries = TagBlocksQueries(driver)
|
||||
}
|
||||
|
||||
private fun createTestDataStore(): DataStore<Preferences> {
|
||||
return PreferenceDataStoreFactory.create { File(tempDir, "test_preferences.preferences_pb") }
|
||||
@AfterEach
|
||||
fun teardown() {
|
||||
Dispatchers.resetMain()
|
||||
driver.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `saveTagBlock inserts new tag block successfully`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
val repository = TagBlockRepository(tagBlocksQueries, testDispatcher, testDispatcher)
|
||||
|
||||
val expirationTime = System.currentTimeMillis() + 86400000
|
||||
repository.saveTagBlock("android", expirationTime)
|
||||
|
||||
val tags = repository.getSavedTags().first()
|
||||
assertThat(tags).containsExactly("android")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `saveTagBlock with null expiration creates permanent block`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
val repository = TagBlockRepository(tagBlocksQueries, testDispatcher, testDispatcher)
|
||||
|
||||
repository.saveTagBlock("kotlin", null)
|
||||
|
||||
@@ -85,8 +70,9 @@ class TagBlockRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `saveTagBlock replaces existing tag block`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
val repository = TagBlockRepository(tagBlocksQueries, testDispatcher, testDispatcher)
|
||||
|
||||
val firstExpiration = System.currentTimeMillis() + 86400000
|
||||
repository.saveTagBlock("rust", firstExpiration)
|
||||
@@ -101,8 +87,9 @@ class TagBlockRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `removeTagBlock deletes tag successfully`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
val repository = TagBlockRepository(tagBlocksQueries, testDispatcher, testDispatcher)
|
||||
|
||||
repository.saveTagBlock("python", null)
|
||||
repository.saveTagBlock("javascript", null)
|
||||
@@ -118,8 +105,9 @@ class TagBlockRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `getSavedTags filters out expired tags`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
val repository = TagBlockRepository(tagBlocksQueries, testDispatcher, testDispatcher)
|
||||
|
||||
val pastTime = System.currentTimeMillis() - 1000
|
||||
val futureTime = System.currentTimeMillis() + 86400000
|
||||
@@ -134,10 +122,31 @@ class TagBlockRepositoryTest {
|
||||
assertThat(tags).doesNotContain("expired-tag")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSavedTags emits updates after mutations`() = runTest {
|
||||
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
val repository = TagBlockRepository(tagBlocksQueries, testDispatcher, testDispatcher)
|
||||
val emissions = mutableListOf<Set<String>>()
|
||||
|
||||
val collector =
|
||||
backgroundScope.launch(testDispatcher) { repository.getSavedTags().take(3).toList(emissions) }
|
||||
|
||||
repository.saveTagBlock("android", null)
|
||||
repository.removeTagBlock("android")
|
||||
|
||||
collector.join()
|
||||
|
||||
assertThat(emissions)
|
||||
.containsExactly(emptySet<String>(), setOf("android"), emptySet<String>())
|
||||
.inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getTagBlocks returns all blocks including expired`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
val repository = TagBlockRepository(tagBlocksQueries, testDispatcher, testDispatcher)
|
||||
|
||||
val pastTime = System.currentTimeMillis() - 1000
|
||||
val futureTime = System.currentTimeMillis() + 86400000
|
||||
@@ -152,10 +161,36 @@ class TagBlockRepositoryTest {
|
||||
assertThat(tagBlocks.map { it.tag }).containsExactly("expired", "active", "permanent")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getTagBlocks emits updates after mutations`() = runTest {
|
||||
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
val repository = TagBlockRepository(tagBlocksQueries, testDispatcher, testDispatcher)
|
||||
val emissions = mutableListOf<List<String>>()
|
||||
val collectedBlocks = mutableListOf<List<dev.msfjarvis.claw.model.TagBlock>>()
|
||||
|
||||
val collector =
|
||||
backgroundScope.launch(testDispatcher) {
|
||||
repository.getTagBlocks().take(3).toList(collectedBlocks)
|
||||
}
|
||||
|
||||
repository.saveTagBlock("kotlin", null)
|
||||
repository.removeTagBlock("kotlin")
|
||||
|
||||
collector.join()
|
||||
|
||||
emissions += collectedBlocks.map { blocks -> blocks.map { it.tag } }
|
||||
|
||||
assertThat(emissions)
|
||||
.containsExactly(emptyList<String>(), listOf("kotlin"), emptyList<String>())
|
||||
.inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `removeExpiredTags only removes expired blocks`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
val repository = TagBlockRepository(tagBlocksQueries, testDispatcher, testDispatcher)
|
||||
|
||||
val pastTime = System.currentTimeMillis() - 1000
|
||||
val futureTime = System.currentTimeMillis() + 86400000
|
||||
@@ -171,128 +206,4 @@ class TagBlockRepositoryTest {
|
||||
assertThat(remaining).hasSize(2)
|
||||
assertThat(remaining.map { it.tag }).containsExactly("future", "permanent")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrates legacy DataStore tags to SQLite`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val legacyTagsKey = stringSetPreferencesKey("tags")
|
||||
|
||||
dataStore.edit { prefs -> prefs[legacyTagsKey] = setOf("android", "kotlin", "java") }
|
||||
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
|
||||
val tags = repository.getSavedTags().first()
|
||||
|
||||
assertThat(tags).containsExactly("android", "kotlin", "java")
|
||||
|
||||
val tagBlocks = repository.getTagBlocks().first()
|
||||
assertThat(tagBlocks.all { it.isPermanent }).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrates JSON DataStore format to SQLite`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val datastoreTagsKey = stringPreferencesKey("tags_with_expiration")
|
||||
|
||||
val expirationTime = System.currentTimeMillis() + 86400000
|
||||
val tagMap = mapOf("rust" to expirationTime, "go" to null)
|
||||
|
||||
dataStore.edit { prefs -> prefs[datastoreTagsKey] = Json.encodeToString(tagMap) }
|
||||
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
|
||||
val tags = repository.getSavedTags().first()
|
||||
assertThat(tags).containsExactly("rust", "go")
|
||||
|
||||
val tagBlocks = repository.getTagBlocks().first()
|
||||
val rustBlock = tagBlocks.find { it.tag == "rust" }
|
||||
val goBlock = tagBlocks.find { it.tag == "go" }
|
||||
|
||||
assertThat(rustBlock?.expirationMillis).isEqualTo(expirationTime)
|
||||
assertThat(goBlock?.isPermanent).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migration only runs once`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val legacyTagsKey = stringSetPreferencesKey("tags")
|
||||
|
||||
dataStore.edit { prefs -> prefs[legacyTagsKey] = setOf("security") }
|
||||
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
|
||||
repository.getSavedTags().first()
|
||||
|
||||
dataStore.edit { prefs -> prefs[legacyTagsKey] = setOf("privacy") }
|
||||
|
||||
val tags = repository.getSavedTags().first()
|
||||
assertThat(tags).containsExactly("security")
|
||||
assertThat(tags).doesNotContain("privacy")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `prefers JSON format over legacy format during migration`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val legacyTagsKey = stringSetPreferencesKey("tags")
|
||||
val datastoreTagsKey = stringPreferencesKey("tags_with_expiration")
|
||||
|
||||
dataStore.edit { prefs ->
|
||||
prefs[legacyTagsKey] = setOf("old-tag")
|
||||
prefs[datastoreTagsKey] = Json.encodeToString(mapOf("new-tag" to null))
|
||||
}
|
||||
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
|
||||
val tags = repository.getSavedTags().first()
|
||||
|
||||
assertThat(tags).containsExactly("new-tag")
|
||||
assertThat(tags).doesNotContain("old-tag")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migration cleans up legacy DataStore after migrating`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val legacyTagsKey = stringSetPreferencesKey("tags")
|
||||
|
||||
dataStore.edit { prefs -> prefs[legacyTagsKey] = setOf("android", "kotlin") }
|
||||
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
repository.getSavedTags().first()
|
||||
|
||||
val prefs = dataStore.data.first()
|
||||
assertThat(prefs[legacyTagsKey]).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migration cleans up JSON DataStore after migrating`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val datastoreTagsKey = stringPreferencesKey("tags_with_expiration")
|
||||
|
||||
dataStore.edit { prefs -> prefs[datastoreTagsKey] = Json.encodeToString(mapOf("rust" to null)) }
|
||||
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
repository.getSavedTags().first()
|
||||
|
||||
val prefs = dataStore.data.first()
|
||||
assertThat(prefs[datastoreTagsKey]).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migration cleans up both DataStore formats when both present`() = runTest {
|
||||
val dataStore = createTestDataStore()
|
||||
val legacyTagsKey = stringSetPreferencesKey("tags")
|
||||
val datastoreTagsKey = stringPreferencesKey("tags_with_expiration")
|
||||
|
||||
dataStore.edit { prefs ->
|
||||
prefs[legacyTagsKey] = setOf("old-tag")
|
||||
prefs[datastoreTagsKey] = Json.encodeToString(mapOf("new-tag" to null))
|
||||
}
|
||||
|
||||
val repository = TagBlockRepository(tagBlocksQueries, dataStore, testDispatcher, testDispatcher)
|
||||
repository.getSavedTags().first()
|
||||
|
||||
val prefs = dataStore.data.first()
|
||||
assertThat(prefs[legacyTagsKey]).isNull()
|
||||
assertThat(prefs[datastoreTagsKey]).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ compose-runtime = "1.11.0-beta02"
|
||||
benchmark = "1.5.0-alpha05"
|
||||
compose-ui = "1.11.0-beta02"
|
||||
coroutines = "1.10.2"
|
||||
datastore = "1.3.0-alpha07"
|
||||
eithernet = "2.0.0"
|
||||
glance = "1.2.0-rc01"
|
||||
haze = "1.7.2"
|
||||
@@ -76,9 +75,6 @@ androidx-compose-ui-unit = { module = "androidx.compose.ui:ui-unit", version.ref
|
||||
androidx-core = "androidx.core:core:1.18.0"
|
||||
androidx-core-ktx = "androidx.core:core-ktx:1.18.0"
|
||||
androidx-core-splashscreen = "androidx.core:core-splashscreen:1.2.0"
|
||||
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" }
|
||||
androidx-datastore-core = { module = "androidx.datastore:datastore-core", version.ref = "datastore" }
|
||||
androidx-datastore-preferences-core = { module = "androidx.datastore:datastore-preferences-core", version.ref = "datastore" }
|
||||
androidx-lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle" }
|
||||
androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
||||
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" }
|
||||
|
||||
Reference in New Issue
Block a user