feat: auto-save/load character to local storage (Closes #28) (#67)

This commit was merged in pull request #67.
This commit is contained in:
2026-03-13 13:59:45 +01:00
parent 16fb0bcb88
commit bd99157f13

View File

@@ -1,23 +1,37 @@
package org.shahondin1624.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.russhwolf.settings.Settings
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch
import org.shahondin1624.lib.functions.DataLoader
import org.shahondin1624.model.EXAMPLE_CHARACTER
import org.shahondin1624.model.charactermodel.ShadowrunCharacter
/**
* ViewModel holding the current character as observable state.
* Foundation for all editing stories (6.2-6.6) and interactive features.
* Auto-saves to local storage on changes (debounced 500ms) and
* auto-loads from storage on launch, falling back to EXAMPLE_CHARACTER.
*/
class CharacterViewModel : ViewModel() {
private val _character = MutableStateFlow(EXAMPLE_CHARACTER)
private val settings = Settings()
private val _character = MutableStateFlow(loadCharacter())
/** Observable character state. */
val character: StateFlow<ShadowrunCharacter> = _character.asStateFlow()
init {
startAutoSave()
}
/**
* Replace the current character entirely.
*/
@@ -31,4 +45,60 @@ class CharacterViewModel : ViewModel() {
fun updateCharacter(transform: (ShadowrunCharacter) -> ShadowrunCharacter) {
_character.value = transform(_character.value)
}
/**
* Save the current character immediately.
*/
fun saveNow() {
saveCharacter(_character.value)
}
/**
* Load character from local storage, falling back to EXAMPLE_CHARACTER.
*/
private fun loadCharacter(): ShadowrunCharacter {
return try {
val json = settings.getStringOrNull(STORAGE_KEY)
if (json != null) {
DataLoader.deserialize(json)
} else {
EXAMPLE_CHARACTER
}
} catch (e: Exception) {
// If deserialization fails (e.g., schema change), fall back to example
EXAMPLE_CHARACTER
}
}
/**
* Save character to local storage as JSON.
*/
private fun saveCharacter(character: ShadowrunCharacter) {
try {
val json = DataLoader.serialize(character)
settings.putString(STORAGE_KEY, json)
} catch (e: Exception) {
// Silently ignore save failures
}
}
/**
* Start auto-saving on character changes with 500ms debounce.
* Drops the initial emission (load) to avoid unnecessary save.
*/
@OptIn(FlowPreview::class)
private fun startAutoSave() {
viewModelScope.launch {
_character
.drop(1) // Skip initial value (already saved or loaded)
.debounce(500)
.collect { character ->
saveCharacter(character)
}
}
}
companion object {
private const val STORAGE_KEY = "shadowrun_character"
}
}