From c16711ebb80411463fce3a0300a9495bbeb3b9a8 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Sat, 9 May 2026 00:46:55 +0530 Subject: [PATCH] feat: adding seperate preference key for collections --- .../kotlin/com/nuvio/app/MainActivity.kt | 2 + ...PlatformLocalAccountDataCleaner.android.kt | 1 + ...CollectionMobileSettingsStorage.android.kt | 26 +++ .../core/storage/LocalAccountDataCleaner.kt | 2 + .../app/core/sync/ProfileSettingsSync.kt | 10 ++ .../collection/CollectionEditorRepository.kt | 6 +- .../collection/CollectionEditorScreen.kt | 4 +- .../CollectionMobileSettingsRepository.kt | 155 ++++++++++++++++++ .../CollectionMobileSettingsStorage.kt | 6 + .../features/collection/CollectionModels.kt | 3 + .../collection/CollectionRepository.kt | 19 ++- .../components/HomeCollectionRowSection.kt | 4 +- .../features/profiles/ProfileRepository.kt | 2 + .../CollectionSourceSerializationTest.kt | 70 ++++++++ .../PlatformLocalAccountDataCleaner.ios.kt | 1 + .../CollectionMobileSettingsStorage.ios.kt | 15 ++ 16 files changed, 314 insertions(+), 12 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.android.kt create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsRepository.kt create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.kt create mode 100644 composeApp/src/iosMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.ios.kt diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/MainActivity.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/MainActivity.kt index e899b044..339340ab 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/MainActivity.kt @@ -13,6 +13,7 @@ import com.nuvio.app.core.auth.AuthStorage import com.nuvio.app.core.deeplink.handleAppUrl import com.nuvio.app.core.storage.PlatformLocalAccountDataCleaner import com.nuvio.app.features.addons.AddonStorage +import com.nuvio.app.features.collection.CollectionMobileSettingsStorage import com.nuvio.app.features.collection.CollectionStorage import com.nuvio.app.features.downloads.DownloadsLiveStatusPlatform import com.nuvio.app.features.downloads.DownloadsPlatformDownloader @@ -83,6 +84,7 @@ class MainActivity : AppCompatActivity() { WatchProgressStorage.initialize(applicationContext) StreamLinkCacheStorage.initialize(applicationContext) PluginStorage.initialize(applicationContext) + CollectionMobileSettingsStorage.initialize(applicationContext) CollectionStorage.initialize(applicationContext) DownloadsStorage.initialize(applicationContext) DownloadsPlatformDownloader.initialize(applicationContext) diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.android.kt index 9edf1191..b7243288 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.android.kt @@ -23,6 +23,7 @@ internal actual object PlatformLocalAccountDataCleaner { "nuvio_episode_release_notifications", "nuvio_episode_release_notifications_platform", "nuvio_watch_progress", + "nuvio_collection_mobile_settings", "nuvio_collections", "nuvio_plugins", ) diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.android.kt new file mode 100644 index 00000000..caaba36c --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.android.kt @@ -0,0 +1,26 @@ +package com.nuvio.app.features.collection + +import android.content.Context +import android.content.SharedPreferences +import com.nuvio.app.core.storage.ProfileScopedKey + +actual object CollectionMobileSettingsStorage { + private const val preferencesName = "nuvio_collection_mobile_settings" + private const val payloadKey = "collection_mobile_settings_payload" + + private var preferences: SharedPreferences? = null + + fun initialize(context: Context) { + preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE) + } + + actual fun loadPayload(): String? = + preferences?.getString(ProfileScopedKey.of(payloadKey), null) + + actual fun savePayload(payload: String) { + preferences + ?.edit() + ?.putString(ProfileScopedKey.of(payloadKey), payload) + ?.apply() + } +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/storage/LocalAccountDataCleaner.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/storage/LocalAccountDataCleaner.kt index 603fce83..8892f6e6 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/storage/LocalAccountDataCleaner.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/storage/LocalAccountDataCleaner.kt @@ -3,6 +3,7 @@ package com.nuvio.app.core.storage import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.catalog.CatalogRepository +import com.nuvio.app.features.collection.CollectionMobileSettingsRepository import com.nuvio.app.features.collection.CollectionRepository import com.nuvio.app.features.details.MetaDetailsRepository import com.nuvio.app.features.details.MetaScreenSettingsRepository @@ -44,6 +45,7 @@ internal object LocalAccountDataCleaner { WatchedRepository.clearLocalState() ContinueWatchingPreferencesRepository.clearLocalState() EpisodeReleaseNotificationsRepository.clearLocalState() + CollectionMobileSettingsRepository.clearLocalState() CollectionRepository.clearLocalState() ThemeSettingsRepository.clearLocalState() PosterCardStyleRepository.clearLocalState() diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/ProfileSettingsSync.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/ProfileSettingsSync.kt index 9dd7a999..58df719e 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/ProfileSettingsSync.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/ProfileSettingsSync.kt @@ -4,6 +4,8 @@ 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.collection.CollectionMobileSettingsRepository +import com.nuvio.app.features.collection.CollectionMobileSettingsStorage import com.nuvio.app.features.details.MetaScreenSettingsStorage import com.nuvio.app.features.details.MetaScreenSettingsRepository import com.nuvio.app.features.mdblist.MdbListMetadataService @@ -158,6 +160,7 @@ object ProfileSettingsSync { TmdbSettingsRepository.uiState.map { "tmdb" }, MdbListSettingsRepository.uiState.map { "mdblist" }, MetaScreenSettingsRepository.uiState.map { "meta" }, + CollectionMobileSettingsRepository.uiState.map { "collection_mobile_settings" }, ContinueWatchingPreferencesRepository.uiState.map { "continue_watching" }, TraktSettingsRepository.uiState.map { "trakt_settings" }, TraktCommentsSettings.enabled.map { "trakt_comments" }, @@ -202,6 +205,7 @@ object ProfileSettingsSync { tmdbSettings = TmdbSettingsStorage.exportToSyncPayload(), mdbListSettings = MdbListSettingsStorage.exportToSyncPayload(), metaScreenSettingsPayload = MetaScreenSettingsStorage.loadPayload().orEmpty().trim(), + collectionMobileSettingsPayload = CollectionMobileSettingsStorage.loadPayload().orEmpty().trim(), continueWatchingSettingsPayload = ContinueWatchingPreferencesStorage.loadPayload().orEmpty().trim(), traktSettingsPayload = TraktSettingsStorage.loadPayload().orEmpty().trim(), traktCommentsSettings = TraktCommentsStorage.exportToSyncPayload(), @@ -232,6 +236,9 @@ object ProfileSettingsSync { MetaScreenSettingsStorage.savePayload(blob.features.metaScreenSettingsPayload) MetaScreenSettingsRepository.onProfileChanged() + CollectionMobileSettingsStorage.savePayload(blob.features.collectionMobileSettingsPayload) + CollectionMobileSettingsRepository.onProfileChanged() + ContinueWatchingPreferencesStorage.savePayload(blob.features.continueWatchingSettingsPayload) ContinueWatchingPreferencesRepository.onProfileChanged() @@ -251,6 +258,7 @@ object ProfileSettingsSync { TmdbSettingsRepository.ensureLoaded() MdbListSettingsRepository.ensureLoaded() MetaScreenSettingsRepository.ensureLoaded() + CollectionMobileSettingsRepository.ensureLoaded() ContinueWatchingPreferencesRepository.ensureLoaded() TraktSettingsRepository.ensureLoaded() TraktCommentsSettings.ensureLoaded() @@ -272,6 +280,7 @@ object ProfileSettingsSync { "tmdb=${TmdbSettingsRepository.uiState.value}", "mdblist=${MdbListSettingsRepository.uiState.value}", "meta=${MetaScreenSettingsRepository.uiState.value}", + "collection_mobile_settings=${CollectionMobileSettingsRepository.uiState.value}", "continue=${ContinueWatchingPreferencesRepository.uiState.value}", "trakt_settings=${TraktSettingsRepository.uiState.value}", "trakt_comments=${TraktCommentsSettings.enabled.value}", @@ -293,6 +302,7 @@ private data class MobileProfileSettingsFeatures( @SerialName("tmdb_settings") val tmdbSettings: JsonObject = JsonObject(emptyMap()), @SerialName("mdblist_settings") val mdbListSettings: JsonObject = JsonObject(emptyMap()), @SerialName("meta_screen_settings_payload") val metaScreenSettingsPayload: String = "", + @SerialName("collection_mobile_settings_payload") val collectionMobileSettingsPayload: String = "", @SerialName("continue_watching_settings_payload") val continueWatchingSettingsPayload: String = "", @SerialName("trakt_settings_payload") val traktSettingsPayload: String = "", @SerialName("trakt_comments_settings") val traktCommentsSettings: JsonObject = JsonObject(emptyMap()), diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorRepository.kt index 0a31a9d7..70b5204f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorRepository.kt @@ -195,10 +195,10 @@ object CollectionEditorRepository { ) } - fun updateFolderFocusGifEnabled(enabled: Boolean) { + fun updateFolderMobileFocusGifEnabled(enabled: Boolean) { val folder = _uiState.value.editingFolder ?: return _uiState.value = _uiState.value.copy( - editingFolder = folder.copy(focusGifEnabled = enabled), + editingFolder = folder.copy(mobileFocusGifEnabled = enabled), ) } @@ -808,6 +808,8 @@ object CollectionEditorRepository { folders = state.folders, ) + CollectionMobileSettingsRepository.replaceCollectionFolderGifSettings(collection.id, collection.folders) + if (state.isNew) { CollectionRepository.addCollection(collection) } else { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt index 1114ac1b..7219395a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt @@ -702,8 +702,8 @@ private fun FolderEditorPage( FolderEditorToggleRow( title = stringResource(Res.string.collections_editor_show_gif_when_configured), subtitle = stringResource(Res.string.collections_editor_show_gif_when_configured_desc), - checked = folder.focusGifEnabled, - onCheckedChange = { CollectionEditorRepository.updateFolderFocusGifEnabled(it) }, + checked = folder.mobileFocusGifEnabled, + onCheckedChange = { CollectionEditorRepository.updateFolderMobileFocusGifEnabled(it) }, ) FolderEditorToggleRow( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsRepository.kt new file mode 100644 index 00000000..c122ae63 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsRepository.kt @@ -0,0 +1,155 @@ +package com.nuvio.app.features.collection + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +data class CollectionMobileSettingsUiState( + val folderGifOverrides: Map = emptyMap(), +) + +object CollectionMobileSettingsRepository { + private val json = Json { + ignoreUnknownKeys = true + encodeDefaults = true + } + + private val _uiState = MutableStateFlow(CollectionMobileSettingsUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + private var hasLoaded = false + + fun ensureLoaded() { + if (hasLoaded) return + loadFromDisk() + } + + fun onProfileChanged() { + loadFromDisk() + CollectionRepository.onMobileSettingsChanged() + } + + fun clearLocalState() { + hasLoaded = false + _uiState.value = CollectionMobileSettingsUiState() + } + + fun isFolderGifEnabled(collectionId: String, folderId: String): Boolean { + ensureLoaded() + return _uiState.value.folderGifOverrides[folderKey(collectionId, folderId)] ?: true + } + + fun applyToCollections(collections: List): List { + ensureLoaded() + return collections.map(::applyToCollection) + } + + fun applyToCollection(collection: Collection): Collection { + ensureLoaded() + return collection.copy( + folders = collection.folders.map { folder -> + folder.copy( + mobileFocusGifEnabled = isFolderGifEnabled( + collectionId = collection.id, + folderId = folder.id, + ), + ) + }, + ) + } + + fun replaceCollectionFolderGifSettings(collectionId: String, folders: List) { + ensureLoaded() + val collectionPrefix = "${collectionId.trim()}$FolderKeySeparator" + val next = _uiState.value.folderGifOverrides + .filterKeys { key -> !key.startsWith(collectionPrefix) } + .toMutableMap() + folders.forEach { folder -> + val key = folderKey(collectionId, folder.id) + if (folder.mobileFocusGifEnabled) { + next.remove(key) + } else { + next[key] = false + } + } + _uiState.value = CollectionMobileSettingsUiState(folderGifOverrides = next) + persist() + CollectionRepository.onMobileSettingsChanged() + } + + private fun loadFromDisk() { + hasLoaded = true + + val payload = CollectionMobileSettingsStorage.loadPayload().orEmpty().trim() + if (payload.isEmpty()) { + _uiState.value = CollectionMobileSettingsUiState() + return + } + + val stored = runCatching { + json.decodeFromString(payload) + }.getOrNull() + + _uiState.value = CollectionMobileSettingsUiState( + folderGifOverrides = stored + ?.folderGifOverrides + .orEmpty() + .mapNotNull { item -> + if (item.collectionId.isBlank() || item.folderId.isBlank()) { + null + } else { + folderKey(item.collectionId, item.folderId) to item.enabled + } + } + .toMap(), + ) + } + + private fun persist() { + if (_uiState.value.folderGifOverrides.isEmpty()) { + CollectionMobileSettingsStorage.savePayload("") + return + } + val payload = StoredCollectionMobileSettingsPayload( + folderGifOverrides = _uiState.value.folderGifOverrides + .mapNotNull { (key, enabled) -> + val parts = key.split(FolderKeySeparator, limit = 2) + val collectionId = parts.getOrNull(0).orEmpty() + val folderId = parts.getOrNull(1).orEmpty() + if (collectionId.isBlank() || folderId.isBlank()) { + null + } else { + StoredFolderGifOverride( + collectionId = collectionId, + folderId = folderId, + enabled = enabled, + ) + } + } + .sortedWith(compareBy { it.collectionId }.thenBy { it.folderId }), + ) + CollectionMobileSettingsStorage.savePayload(json.encodeToString(payload)) + } + + private fun folderKey(collectionId: String, folderId: String): String = + "${collectionId.trim()}$FolderKeySeparator${folderId.trim()}" +} + +private const val FolderKeySeparator = "\u001F" + +@Serializable +private data class StoredCollectionMobileSettingsPayload( + @SerialName("folder_gif_overrides") val folderGifOverrides: List = emptyList(), +) + +@Serializable +private data class StoredFolderGifOverride( + @SerialName("collection_id") val collectionId: String, + @SerialName("folder_id") val folderId: String, + val enabled: Boolean = true, +) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.kt new file mode 100644 index 00000000..58ac9020 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.kt @@ -0,0 +1,6 @@ +package com.nuvio.app.features.collection + +internal expect object CollectionMobileSettingsStorage { + fun loadPayload(): String? + fun savePayload(payload: String) +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionModels.kt index ba9080d6..5ec3b606 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionModels.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable import com.nuvio.app.features.home.PosterShape import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient enum class FolderViewMode { TABBED_GRID, @@ -168,6 +169,8 @@ data class CollectionFolder( val coverImageUrl: String? = null, val focusGifUrl: String? = null, val focusGifEnabled: Boolean = true, + @Transient + val mobileFocusGifEnabled: Boolean = true, val coverEmoji: String? = null, val tileShape: String = "poster", val hideTitle: Boolean = false, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionRepository.kt index 39916184..270e9781 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionRepository.kt @@ -52,7 +52,8 @@ object CollectionRepository { runCatching { val parsed = json.parseToJsonElement(payload) rawCollectionsJson = parsed - _collections.value = json.decodeFromString>(payload) + val decoded = json.decodeFromString>(payload) + _collections.value = CollectionMobileSettingsRepository.applyToCollections(decoded) }.onFailure { e -> log.e(e) { "Failed to load collections from storage" } } @@ -75,14 +76,15 @@ object CollectionRepository { fun addCollection(collection: Collection) { ensureLoaded() - _collections.value = _collections.value + collection + _collections.value = _collections.value + CollectionMobileSettingsRepository.applyToCollection(collection) persist() } fun updateCollection(collection: Collection) { ensureLoaded() + val decorated = CollectionMobileSettingsRepository.applyToCollection(collection) _collections.value = _collections.value.map { - if (it.id == collection.id) collection else it + if (it.id == collection.id) decorated else it } persist() } @@ -95,7 +97,7 @@ object CollectionRepository { fun setCollections(collections: List) { ensureLoaded() - _collections.value = collections + _collections.value = CollectionMobileSettingsRepository.applyToCollections(collections) persist() } @@ -127,7 +129,7 @@ object CollectionRepository { return runCatching { rawCollectionsJson = json.parseToJsonElement(jsonString) val imported = json.decodeFromString>(jsonString) - _collections.value = imported + _collections.value = CollectionMobileSettingsRepository.applyToCollections(imported) persist() imported } @@ -262,10 +264,15 @@ object CollectionRepository { internal fun applyFromRemote(collections: List, rawJson: JsonElement) { rawCollectionsJson = rawJson - _collections.value = collections + _collections.value = CollectionMobileSettingsRepository.applyToCollections(collections) persist(sync = false) } + internal fun onMobileSettingsChanged() { + if (!hasLoaded) return + _collections.value = CollectionMobileSettingsRepository.applyToCollections(_collections.value) + } + private fun ensureLoaded() { if (!hasLoaded) initialize() } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt index 37d3fced..dd053375 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCollectionRowSection.kt @@ -186,7 +186,7 @@ private fun CollectionFolderCard( } private fun collectionFolderCardImageUrl(folder: CollectionFolder): String? { - return if (folder.focusGifEnabled) { + return if (folder.mobileFocusGifEnabled) { firstNonBlank(folder.focusGifUrl, folder.coverImageUrl) } else { firstNonBlank(folder.coverImageUrl) @@ -202,5 +202,5 @@ private fun isAnimatedCollectionFolderImage( imageUrl: String, ): Boolean { val gifUrl = firstNonBlank(folder.focusGifUrl) ?: return false - return folder.focusGifEnabled && imageUrl == gifUrl + return folder.mobileFocusGifEnabled && imageUrl == gifUrl } 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 0cb6cc27..5760e73e 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 @@ -6,6 +6,7 @@ import com.nuvio.app.core.auth.AuthState import com.nuvio.app.core.auth.isAnonymous import com.nuvio.app.core.network.SupabaseProvider import com.nuvio.app.features.addons.AddonRepository +import com.nuvio.app.features.collection.CollectionMobileSettingsRepository import com.nuvio.app.features.collection.CollectionRepository import com.nuvio.app.features.downloads.DownloadsRepository import com.nuvio.app.features.details.MetaScreenSettingsRepository @@ -156,6 +157,7 @@ object ProfileRepository { TraktAuthRepository.onProfileChanged() SearchHistoryRepository.onProfileChanged() CollectionRepository.onProfileChanged() + CollectionMobileSettingsRepository.onProfileChanged() DownloadsRepository.onProfileChanged() } diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/collection/CollectionSourceSerializationTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/collection/CollectionSourceSerializationTest.kt index 66f227dd..5f83cd99 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/collection/CollectionSourceSerializationTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/collection/CollectionSourceSerializationTest.kt @@ -3,8 +3,13 @@ package com.nuvio.app.features.collection import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -178,4 +183,69 @@ class CollectionSourceSerializationTest { assertTrue(merged.contains(""""customField":"keep-me"""")) assertTrue(merged.contains(""""traktListId":123456""")) } + + @Test + fun mobileGifToggleDoesNotEnterCollectionJsonOrOverwriteTvGifToggle() { + val raw = json.parseToJsonElement( + """ + [ + { + "id": "collection-1", + "title": "Favorites", + "folders": [ + { + "id": "folder-1", + "title": "Movies", + "coverImageUrl": "https://example.com/poster.jpg", + "focusGifUrl": "https://example.com/focus.gif", + "focusGifEnabled": true + } + ] + } + ] + """.trimIndent(), + ) + val collection = json.decodeFromString>(raw.toString()).single() + val mobileDisabled = collection.copy( + folders = collection.folders.map { folder -> + folder.copy(mobileFocusGifEnabled = false) + }, + ) + + val merged = CollectionJsonPreserver.merge(json, raw, listOf(mobileDisabled)) + val mergedFolder = merged + .single() + .jsonObject["folders"]!! + .jsonArray + .single() + .jsonObject + + assertTrue(mergedFolder["focusGifEnabled"]!!.jsonPrimitive.boolean) + assertTrue(mergedFolder["mobileFocusGifEnabled"] == null) + } + + @Test + fun mobileGifToggleDefaultsIndependentOfTvGifToggle() { + val payload = """ + [ + { + "id": "collection-1", + "title": "Favorites", + "folders": [ + { + "id": "folder-1", + "title": "Movies", + "focusGifUrl": "https://example.com/focus.gif", + "focusGifEnabled": false + } + ] + } + ] + """.trimIndent() + + val folder = json.decodeFromString>(payload).single().folders.single() + + assertFalse(folder.focusGifEnabled) + assertTrue(folder.mobileFocusGifEnabled) + } } diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt index 553140ee..b3e60b1d 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt @@ -46,6 +46,7 @@ internal actual object PlatformLocalAccountDataCleaner { "trakt_auth_payload", "trakt_library_payload", "trakt_settings_payload", + "collection_mobile_settings_payload", "collections_payload", ) diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.ios.kt new file mode 100644 index 00000000..e214807d --- /dev/null +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/collection/CollectionMobileSettingsStorage.ios.kt @@ -0,0 +1,15 @@ +package com.nuvio.app.features.collection + +import com.nuvio.app.core.storage.ProfileScopedKey +import platform.Foundation.NSUserDefaults + +actual object CollectionMobileSettingsStorage { + private const val payloadKey = "collection_mobile_settings_payload" + + actual fun loadPayload(): String? = + NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(payloadKey)) + + actual fun savePayload(payload: String) { + NSUserDefaults.standardUserDefaults.setObject(payload, forKey = ProfileScopedKey.of(payloadKey)) + } +}