feat: Update storage methods to support profile-specific data; modify load and save functions across addons, library, and watch progress

This commit is contained in:
tapframe 2026-03-29 13:43:24 +05:30
parent c1337e8b70
commit 35d6f65993
13 changed files with 89 additions and 43 deletions

View file

@ -18,19 +18,19 @@ actual object AddonStorage {
preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
}
actual fun loadInstalledAddonUrls(): List<String> =
actual fun loadInstalledAddonUrls(profileId: Int): List<String> =
preferences
?.getString(addonUrlsKey, null)
?.getString("${addonUrlsKey}_$profileId", null)
.orEmpty()
.lineSequence()
.map { it.trim() }
.filter { it.isNotEmpty() }
.toList()
actual fun saveInstalledAddonUrls(urls: List<String>) {
actual fun saveInstalledAddonUrls(profileId: Int, urls: List<String>) {
preferences
?.edit()
?.putString(addonUrlsKey, urls.joinToString(separator = "\n"))
?.putString("${addonUrlsKey}_$profileId", urls.joinToString(separator = "\n"))
?.apply()
}
}

View file

@ -5,7 +5,7 @@ import android.content.SharedPreferences
actual object LibraryStorage {
private const val preferencesName = "nuvio_library"
private const val payloadKey = "library_payload"
private fun payloadKey(profileId: Int) = "library_payload_$profileId"
private var preferences: SharedPreferences? = null
@ -13,13 +13,13 @@ actual object LibraryStorage {
preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
}
actual fun loadPayload(): String? =
preferences?.getString(payloadKey, null)
actual fun loadPayload(profileId: Int): String? =
preferences?.getString(payloadKey(profileId), null)
actual fun savePayload(payload: String) {
actual fun savePayload(profileId: Int, payload: String) {
preferences
?.edit()
?.putString(payloadKey, payload)
?.putString(payloadKey(profileId), payload)
?.apply()
}
}

View file

@ -13,13 +13,13 @@ actual object WatchProgressStorage {
preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
}
actual fun loadPayload(): String? =
preferences?.getString(payloadKey, null)
actual fun loadPayload(profileId: Int): String? =
preferences?.getString("${payloadKey}_$profileId", null)
actual fun savePayload(payload: String) {
actual fun savePayload(profileId: Int, payload: String) {
preferences
?.edit()
?.putString(payloadKey, payload)
?.putString("${payloadKey}_$profileId", payload)
?.apply()
}
}

View file

@ -1,8 +1,8 @@
package com.nuvio.app.features.addons
internal expect object AddonStorage {
fun loadInstalledAddonUrls(): List<String>
fun saveInstalledAddonUrls(urls: List<String>)
fun loadInstalledAddonUrls(profileId: Int): List<String>
fun saveInstalledAddonUrls(profileId: Int, urls: List<String>)
}
expect suspend fun httpGetText(url: String): String

View file

@ -48,13 +48,15 @@ object AddonRepository {
private var initialized = false
private var pulledFromServer = false
private var currentProfileId: Int = 1
fun initialize() {
if (initialized) return
initialized = true
log.d { "initialize() — loading local addons" }
currentProfileId = ProfileRepository.activeProfileId
log.d { "initialize() — loading local addons for profile $currentProfileId" }
val storedUrls = AddonStorage.loadInstalledAddonUrls()
val storedUrls = AddonStorage.loadInstalledAddonUrls(currentProfileId)
log.d { "initialize() — local addon count: ${storedUrls.size}" }
if (storedUrls.isEmpty()) return
@ -70,7 +72,16 @@ object AddonRepository {
storedUrls.forEach(::refreshAddon)
}
fun onProfileChanged(profileId: Int) {
if (profileId == currentProfileId && initialized) return
currentProfileId = profileId
initialized = false
pulledFromServer = false
_uiState.value = AddonsUiState()
}
suspend fun pullFromServer(profileId: Int) {
currentProfileId = profileId
log.i { "pullFromServer() — profileId=$profileId, initialized=$initialized, pulledFromServer=$pulledFromServer" }
runCatching {
val rows = SupabaseProvider.client.postgrest
@ -86,7 +97,7 @@ object AddonRepository {
urls.forEachIndexed { i, u -> log.d { " server[$i]: $u" } }
if (urls.isEmpty() && !pulledFromServer) {
val localUrls = AddonStorage.loadInstalledAddonUrls()
val localUrls = AddonStorage.loadInstalledAddonUrls(profileId)
log.i { "pullFromServer() — server empty, local has ${localUrls.size} addons" }
if (localUrls.isNotEmpty()) {
log.i { "pullFromServer() — migrating local addons to server for profile $profileId" }
@ -272,6 +283,7 @@ object AddonRepository {
private fun persist() {
AddonStorage.saveInstalledAddonUrls(
currentProfileId,
_uiState.value.addons.map { it.manifestUrl },
)
}

View file

@ -53,13 +53,25 @@ object LibraryRepository {
val uiState: StateFlow<LibraryUiState> = _uiState.asStateFlow()
private var hasLoaded = false
private var currentProfileId: Int = 1
private var itemsById: MutableMap<String, LibraryItem> = mutableMapOf()
fun ensureLoaded() {
if (hasLoaded) return
hasLoaded = true
loadFromDisk(ProfileRepository.activeProfileId)
}
val payload = LibraryStorage.loadPayload().orEmpty().trim()
fun onProfileChanged(profileId: Int) {
if (profileId == currentProfileId && hasLoaded) return
loadFromDisk(profileId)
}
private fun loadFromDisk(profileId: Int) {
currentProfileId = profileId
hasLoaded = true
itemsById.clear()
val payload = LibraryStorage.loadPayload(profileId).orEmpty().trim()
if (payload.isNotEmpty()) {
val items = runCatching {
json.decodeFromString<StoredLibraryPayload>(payload).items
@ -71,6 +83,7 @@ object LibraryRepository {
}
suspend fun pullFromServer(profileId: Int) {
currentProfileId = profileId
runCatching {
val params = buildJsonObject {
put("p_profile_id", profileId)
@ -163,6 +176,7 @@ object LibraryRepository {
private fun persist() {
LibraryStorage.savePayload(
currentProfileId,
json.encodeToString(
StoredLibraryPayload(
items = itemsById.values.sortedByDescending { it.savedAtEpochMs },

View file

@ -1,6 +1,6 @@
package com.nuvio.app.features.library
internal expect object LibraryStorage {
fun loadPayload(): String?
fun savePayload(payload: String)
fun loadPayload(profileId: Int): String?
fun savePayload(profileId: Int, payload: String)
}

View file

@ -4,7 +4,10 @@ import co.touchlab.kermit.Logger
import com.nuvio.app.core.auth.AuthRepository
import com.nuvio.app.core.auth.AuthState
import com.nuvio.app.core.network.SupabaseProvider
import com.nuvio.app.features.addons.AddonRepository
import com.nuvio.app.features.library.LibraryRepository
import com.nuvio.app.features.watched.WatchedRepository
import com.nuvio.app.features.watchprogress.WatchProgressRepository
import io.github.jan.supabase.postgrest.postgrest
import io.github.jan.supabase.postgrest.rpc
import kotlinx.coroutines.CoroutineScope
@ -108,6 +111,9 @@ object ProfileRepository {
)
persist()
WatchedRepository.onProfileChanged(profileIndex)
LibraryRepository.onProfileChanged(profileIndex)
WatchProgressRepository.onProfileChanged(profileIndex)
AddonRepository.onProfileChanged(profileIndex)
}
suspend fun pushProfiles(profiles: List<ProfilePushPayload>) {

View file

@ -45,22 +45,35 @@ object WatchProgressRepository {
val uiState: StateFlow<WatchProgressUiState> = _uiState.asStateFlow()
private var hasLoaded = false
private var currentProfileId: Int = 1
private var entriesByVideoId: MutableMap<String, WatchProgressEntry> = mutableMapOf()
fun ensureLoaded() {
if (hasLoaded) return
loadFromDisk(ProfileRepository.activeProfileId)
}
fun onProfileChanged(profileId: Int) {
if (profileId == currentProfileId && hasLoaded) return
loadFromDisk(profileId)
}
private fun loadFromDisk(profileId: Int) {
currentProfileId = profileId
hasLoaded = true
entriesByVideoId.clear()
val payload = WatchProgressStorage.loadPayload().orEmpty().trim()
if (payload.isEmpty()) return
entriesByVideoId = WatchProgressCodec.decodeEntries(payload)
.associateBy { it.videoId }
.toMutableMap()
val payload = WatchProgressStorage.loadPayload(profileId).orEmpty().trim()
if (payload.isNotEmpty()) {
entriesByVideoId = WatchProgressCodec.decodeEntries(payload)
.associateBy { it.videoId }
.toMutableMap()
}
publish()
}
suspend fun pullFromServer(profileId: Int) {
currentProfileId = profileId
runCatching {
val params = buildJsonObject { put("p_profile_id", profileId) }
val result = SupabaseProvider.client.postgrest.rpc("sync_pull_watch_progress", params)
@ -292,6 +305,7 @@ object WatchProgressRepository {
private fun persist() {
WatchProgressStorage.savePayload(
currentProfileId,
WatchProgressCodec.encodeEntries(entriesByVideoId.values),
)
}

View file

@ -1,6 +1,6 @@
package com.nuvio.app.features.watchprogress
internal expect object WatchProgressStorage {
fun loadPayload(): String?
fun savePayload(payload: String)
fun loadPayload(profileId: Int): String?
fun savePayload(profileId: Int, payload: String)
}

View file

@ -14,19 +14,19 @@ import platform.posix.memcpy
actual object AddonStorage {
private const val addonUrlsKey = "installed_manifest_urls"
actual fun loadInstalledAddonUrls(): List<String> =
actual fun loadInstalledAddonUrls(profileId: Int): List<String> =
NSUserDefaults.standardUserDefaults
.stringForKey(addonUrlsKey)
.stringForKey("${addonUrlsKey}_$profileId")
.orEmpty()
.lineSequence()
.map { it.trim() }
.filter { it.isNotEmpty() }
.toList()
actual fun saveInstalledAddonUrls(urls: List<String>) {
actual fun saveInstalledAddonUrls(profileId: Int, urls: List<String>) {
NSUserDefaults.standardUserDefaults.setObject(
urls.joinToString(separator = "\n"),
forKey = addonUrlsKey,
forKey = "${addonUrlsKey}_$profileId",
)
}
}

View file

@ -3,12 +3,12 @@ package com.nuvio.app.features.library
import platform.Foundation.NSUserDefaults
actual object LibraryStorage {
private const val payloadKey = "library_payload"
private fun payloadKey(profileId: Int) = "library_payload_$profileId"
actual fun loadPayload(): String? =
NSUserDefaults.standardUserDefaults.stringForKey(payloadKey)
actual fun loadPayload(profileId: Int): String? =
NSUserDefaults.standardUserDefaults.stringForKey(payloadKey(profileId))
actual fun savePayload(payload: String) {
NSUserDefaults.standardUserDefaults.setObject(payload, forKey = payloadKey)
actual fun savePayload(profileId: Int, payload: String) {
NSUserDefaults.standardUserDefaults.setObject(payload, forKey = payloadKey(profileId))
}
}

View file

@ -5,10 +5,10 @@ import platform.Foundation.NSUserDefaults
actual object WatchProgressStorage {
private const val payloadKey = "watch_progress_payload"
actual fun loadPayload(): String? =
NSUserDefaults.standardUserDefaults.stringForKey(payloadKey)
actual fun loadPayload(profileId: Int): String? =
NSUserDefaults.standardUserDefaults.stringForKey("${payloadKey}_$profileId")
actual fun savePayload(payload: String) {
NSUserDefaults.standardUserDefaults.setObject(payload, forKey = payloadKey)
actual fun savePayload(profileId: Int, payload: String) {
NSUserDefaults.standardUserDefaults.setObject(payload, forKey = "${payloadKey}_$profileId")
}
}