diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.android.kt index 38daa2802..00b5ebed0 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.android.kt @@ -18,19 +18,19 @@ actual object AddonStorage { preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE) } - actual fun loadInstalledAddonUrls(): List = + actual fun loadInstalledAddonUrls(profileId: Int): List = preferences - ?.getString(addonUrlsKey, null) + ?.getString("${addonUrlsKey}_$profileId", null) .orEmpty() .lineSequence() .map { it.trim() } .filter { it.isNotEmpty() } .toList() - actual fun saveInstalledAddonUrls(urls: List) { + actual fun saveInstalledAddonUrls(profileId: Int, urls: List) { preferences ?.edit() - ?.putString(addonUrlsKey, urls.joinToString(separator = "\n")) + ?.putString("${addonUrlsKey}_$profileId", urls.joinToString(separator = "\n")) ?.apply() } } diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/library/LibraryStorage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/library/LibraryStorage.android.kt index 565b8585e..dbe104e0e 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/library/LibraryStorage.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/library/LibraryStorage.android.kt @@ -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() } } diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.android.kt index 03ab2fb36..5cc071298 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.android.kt @@ -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() } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.kt index 8969826d9..09d2e2159 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.kt @@ -1,8 +1,8 @@ package com.nuvio.app.features.addons internal expect object AddonStorage { - fun loadInstalledAddonUrls(): List - fun saveInstalledAddonUrls(urls: List) + fun loadInstalledAddonUrls(profileId: Int): List + fun saveInstalledAddonUrls(profileId: Int, urls: List) } expect suspend fun httpGetText(url: String): String diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonRepository.kt index a4ac08510..a49c70f85 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/addons/AddonRepository.kt @@ -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 }, ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt index 20d43a42b..454a17fb5 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt @@ -53,13 +53,25 @@ object LibraryRepository { val uiState: StateFlow = _uiState.asStateFlow() private var hasLoaded = false + private var currentProfileId: Int = 1 private var itemsById: MutableMap = 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(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 }, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryStorage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryStorage.kt index 4b18bf758..61673096e 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryStorage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryStorage.kt @@ -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) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt index 364d319f5..49d447325 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt @@ -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) { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt index ba8a128ed..f2262357b 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt @@ -45,22 +45,35 @@ object WatchProgressRepository { val uiState: StateFlow = _uiState.asStateFlow() private var hasLoaded = false + private var currentProfileId: Int = 1 private var entriesByVideoId: MutableMap = 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), ) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.kt index 260e8b73f..150f936b8 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.kt @@ -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) } diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.ios.kt index 0fa2f7162..b7b046a7b 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/addons/AddonPlatform.ios.kt @@ -14,19 +14,19 @@ import platform.posix.memcpy actual object AddonStorage { private const val addonUrlsKey = "installed_manifest_urls" - actual fun loadInstalledAddonUrls(): List = + actual fun loadInstalledAddonUrls(profileId: Int): List = NSUserDefaults.standardUserDefaults - .stringForKey(addonUrlsKey) + .stringForKey("${addonUrlsKey}_$profileId") .orEmpty() .lineSequence() .map { it.trim() } .filter { it.isNotEmpty() } .toList() - actual fun saveInstalledAddonUrls(urls: List) { + actual fun saveInstalledAddonUrls(profileId: Int, urls: List) { NSUserDefaults.standardUserDefaults.setObject( urls.joinToString(separator = "\n"), - forKey = addonUrlsKey, + forKey = "${addonUrlsKey}_$profileId", ) } } diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/library/LibraryStorage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/library/LibraryStorage.ios.kt index 8e2e157f6..f2f18adf3 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/library/LibraryStorage.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/library/LibraryStorage.ios.kt @@ -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)) } } diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.ios.kt index e082bc74b..fcfa7daf4 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressStorage.ios.kt @@ -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") } }