From 73fe5e85fd319d3ed3b515d1f8740ad0b0d5b97f Mon Sep 17 00:00:00 2001 From: shahondin1624 Date: Fri, 13 Mar 2026 13:59:26 +0100 Subject: [PATCH] feat: add auto-save/load character to local storage (Closes #28) Use multiplatform-settings for cross-platform persistence. Character auto-loads from storage on launch (falls back to EXAMPLE_CHARACTER). Auto-saves on every change with 500ms debounce via viewModelScope. Uses DataLoader.serialize/deserialize for JSON conversion. Works on all platform targets (Android, Desktop, Web, iOS). Co-Authored-By: Claude Opus 4.6 --- .../viewmodel/CharacterViewModel.kt | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/sharedUI/src/commonMain/kotlin/org/shahondin1624/viewmodel/CharacterViewModel.kt b/sharedUI/src/commonMain/kotlin/org/shahondin1624/viewmodel/CharacterViewModel.kt index 3ea4cbf..ae4a472 100644 --- a/sharedUI/src/commonMain/kotlin/org/shahondin1624/viewmodel/CharacterViewModel.kt +++ b/sharedUI/src/commonMain/kotlin/org/shahondin1624/viewmodel/CharacterViewModel.kt @@ -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 = _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" + } }