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 package org.shahondin1624.viewmodel
import androidx.lifecycle.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.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow 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.EXAMPLE_CHARACTER
import org.shahondin1624.model.charactermodel.ShadowrunCharacter import org.shahondin1624.model.charactermodel.ShadowrunCharacter
/** /**
* ViewModel holding the current character as observable state. * 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() { class CharacterViewModel : ViewModel() {
private val _character = MutableStateFlow(EXAMPLE_CHARACTER) private val settings = Settings()
private val _character = MutableStateFlow(loadCharacter())
/** Observable character state. */ /** Observable character state. */
val character: StateFlow<ShadowrunCharacter> = _character.asStateFlow() val character: StateFlow<ShadowrunCharacter> = _character.asStateFlow()
init {
startAutoSave()
}
/** /**
* Replace the current character entirely. * Replace the current character entirely.
*/ */
@@ -31,4 +45,60 @@ class CharacterViewModel : ViewModel() {
fun updateCharacter(transform: (ShadowrunCharacter) -> ShadowrunCharacter) { fun updateCharacter(transform: (ShadowrunCharacter) -> ShadowrunCharacter) {
_character.value = transform(_character.value) _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"
}
} }