diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index e2b65ae..56eaa82 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -23,10 +23,7 @@ kotlin { implementation(libs.androidx.lifecycle.viewmodel) implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.navigation.compose) - implementation(libs.androidx.datastore.preferences) - - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") - + implementation(libs.kotlinx.serialization.json) } commonTest.dependencies { @@ -61,7 +58,7 @@ compose.desktop { } buildTypes.release.proguard { - obfuscate.set(false) // Obfuscation crash when try to use datastore. + obfuscate.set(true) // Obfuscation crash at runtime when try to use datastore. configurationFiles.from(project.file("compose-desktop.pro")) } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/createDataStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/createDataStore.kt deleted file mode 100644 index d1d5472..0000000 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/createDataStore.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.pixelized.desktop.lwa - -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.PreferenceDataStoreFactory -import androidx.datastore.preferences.core.Preferences -import com.pixelized.desktop.lwa.utils.OperatingSystem -import okio.Path.Companion.toPath - -fun createDataStore(producePath: () -> String): DataStore { - return PreferenceDataStoreFactory.createWithPath(produceFile = { producePath().toPath() }) -} - -fun dataStorePath(): String { - val root = when { - OperatingSystem.isWindows() -> "${OperatingSystem.home}\\AppData\\Roaming\\Pixelized\\" - OperatingSystem.isMacintosh() -> "${OperatingSystem.home}/Library/Pixelized/" - else -> "" - } - return "${root}characterssheet.preferences_pb" -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/StorePath.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/StorePath.kt new file mode 100644 index 0000000..ffd5f8b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/StorePath.kt @@ -0,0 +1,27 @@ +package com.pixelized.desktop.lwa.repository + +private fun operatingSystem( + name: String = System.getProperty("os.name") +): OperatingSystem { + return when { + name.contains(other = "win", ignoreCase = true) -> OperatingSystem.Windows + name.contains(other = "mac", ignoreCase = true) -> OperatingSystem.Macintosh + else -> error("Unsupported operating system: $name") + } +} + +enum class OperatingSystem( + val home: String, +) { + Windows(home = System.getProperty("user.home")), + Macintosh(home = System.getProperty("user.home")), +} + +fun storePath( + operatingSystem: OperatingSystem = operatingSystem() +): String { + return when (operatingSystem) { + OperatingSystem.Windows -> "${operatingSystem.home}\\AppData\\Roaming\\Pixelized\\" + OperatingSystem.Macintosh -> "${operatingSystem.home}/Library/Pixelized/" + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetPreference.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetPreference.kt deleted file mode 100644 index 8357108..0000000 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetPreference.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.pixelized.desktop.lwa.repository.characterSheet - -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.first -import kotlinx.coroutines.flow.map -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json - -class CharacterSheetPreference( - private val dataStore: DataStore, -) { - suspend fun save(sheets: List) { - dataStore.edit { - it[characterSheetKey] = Json.encodeToString(sheets) - } - } - - suspend fun load(): List { - return loadFlow().first() - } - - fun loadFlow(): Flow> { - return dataStore.data.map { - it[characterSheetKey]?.let { json -> - Json.decodeFromString>(json) - } ?: emptyList() - } - } - - companion object { - private val characterSheetKey = stringPreferencesKey("CharacterSheetsPrefKey") - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt index 28c938b..92dd144 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt @@ -1,7 +1,5 @@ package com.pixelized.desktop.lwa.repository.characterSheet -import com.pixelized.desktop.lwa.createDataStore -import com.pixelized.desktop.lwa.dataStorePath import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted @@ -11,11 +9,9 @@ import kotlinx.coroutines.flow.stateIn object CharacterSheetRepository { private val scope = CoroutineScope(Dispatchers.IO) - private val preferences = CharacterSheetPreference( - dataStore = createDataStore { dataStorePath() } - ) + private val store = CharacterSheetStore() - private val sheets = preferences.loadFlow() + private val sheets = store.loadFlow() .stateIn( scope = scope, started = SharingStarted.Eagerly, @@ -38,8 +34,8 @@ object CharacterSheetRepository { ) } - suspend fun save(characterSheet: CharacterSheet) { - val savedSheets = preferences.load().toMutableList() + fun save(characterSheet: CharacterSheet) { + val savedSheets = store.load().toMutableList() val savedIndex = savedSheets.indexOfFirst { it.id == characterSheet.id } if (savedIndex >= 0) { // this sheet is already saved. update it @@ -49,13 +45,13 @@ object CharacterSheetRepository { savedSheets.add(characterSheet) } // save the list of characters sheet. - preferences.save(sheets = savedSheets) + store.save(sheets = savedSheets) } - suspend fun delete(id: String) { - val savedSheets = preferences.load().toMutableList() + fun delete(id: String) { + val savedSheets = store.load().toMutableList() savedSheets.removeIf { it.id == id } - preferences.save(sheets = savedSheets) + store.save(sheets = savedSheets) } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt new file mode 100644 index 0000000..a632c3a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt @@ -0,0 +1,69 @@ +package com.pixelized.desktop.lwa.repository.characterSheet + +import com.pixelized.desktop.lwa.repository.storePath +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File + +class CharacterSheetStore { + private val root = File(storePath()).also { it.mkdirs() } + private val files = File(root, NAME).also { it.createNewFile() } + private val flow = MutableStateFlow(value = load()) + + @Throws( + CharacterSheetStoreException::class, + FileWriteException::class, + JsonConversionException::class, + ) + fun save(sheets: List) { + val json = try { + Json.encodeToString(sheets) + } catch (exception: Exception) { + throw JsonConversionException(root = exception) + } + try { + files.writeText( + text = json, + charset = Charsets.UTF_8, + ) + } catch (exception: Exception) { + throw FileWriteException(root = exception) + } + flow.value = sheets + } + + @Throws( + CharacterSheetStoreException::class, + FileReadException::class, + JsonConversionException::class, + ) + fun load(): List { + val json = try { + files.readText(charset = Charsets.UTF_8) + } catch (exception: Exception) { + throw FileReadException(root = exception) + } + return if (json.isBlank()) { + emptyList() + } else { + try { + Json.decodeFromString>(json) + } catch (exception: Exception) { + throw JsonConversionException(root = exception) + } + } + } + + fun loadFlow(): StateFlow> = flow + + companion object { + private const val NAME = "characters_sheet.json" + } + + sealed class CharacterSheetStoreException(root: Exception) : Exception(root) + class JsonConversionException(root: Exception) : CharacterSheetStoreException(root) + class FileWriteException(root: Exception) : CharacterSheetStoreException(root) + class FileReadException(root: Exception) : CharacterSheetStoreException(root) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/SystemExt.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/SystemExt.kt deleted file mode 100644 index a59a3c5..0000000 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/utils/SystemExt.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.pixelized.desktop.lwa.utils - -object OperatingSystem { - private val name get() = System.getProperty("os.name") - - val home: String - get() = when { - isWindows() || isMacintosh() -> System.getProperty("user.home") - else -> "" - } - - fun isWindows(): Boolean = name.contains("win", ignoreCase = true) - fun isMacintosh(): Boolean = name.contains("mac", ignoreCase = true) -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c40d174..64e71f8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] kotlin = "2.0.21" kotlinx-coroutines = "1.9.0" +kotlinx-json = "1.7.3" junit = "4.13.2" compose-multiplatform = "1.7.0" androidx-lifecycle = "2.8.3" androidx-navigation = "2.8.0-alpha10" -androidx-datastore = "1.1.1" [libraries] # Test @@ -17,8 +17,8 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } androidx-navigation-compose = { group = "org.jetbrains.androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation" } -androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "androidx-datastore" } kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-json" } [plugins] composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }