From 8d029c40d1b4e28f2ddf82c9abf364a34558fd2f Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 7 Jul 2026 12:31:09 -0600 Subject: [PATCH 1/4] DataStoreHelper: migrate to kotlinx serialization --- .../cloudstream3/utils/DataStoreHelper.kt | 330 ++++++++++-------- 1 file changed, 180 insertions(+), 150 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 19caead21ee..d134b9b4384 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.utils import android.content.Context +import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.CloudStreamApp.Companion.context @@ -31,6 +32,12 @@ import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.VideoWatchState import com.lagradost.cloudstream3.utils.AppContextUtils.filterProviderByPreferredMedia import com.lagradost.cloudstream3.utils.downloader.DownloadObjects +import com.lagradost.cloudstream3.utils.serializers.WriteOnlySerializer +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KeepGeneratedSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import java.util.Calendar import java.util.Date import java.util.GregorianCalendar @@ -43,17 +50,18 @@ const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" const val RESULT_SUBSCRIBED_STATE_DATA = "result_subscribed_state_data" const val RESULT_FAVORITES_STATE_DATA = "result_favorites_state_data" -const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes +const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // Changed due to id changes const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching" const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated" const val RESULT_EPISODE = "result_episode" const val RESULT_SEASON = "result_season" const val RESULT_DUB = "result_dub" const val KEY_RESULT_SORT = "result_sort" -const val USER_PINNED_PROVIDERS = "user_pinned_providers" //key for pinned user set +const val USER_PINNED_PROVIDERS = "user_pinned_providers" // Key for pinned user set class UserPreferenceDelegate( - private val key: String, private val default: T //, private val klass: KClass + private val key: String, + private val default: T, ) { private val klass: KClass = default::class private val realKey get() = "${DataStoreHelper.currentAccount}/$key" @@ -63,13 +71,11 @@ class UserPreferenceDelegate( operator fun setValue( self: Any?, property: KProperty<*>, - t: T? + t: T?, ) { if (t == null) { removeKey(realKey) - } else { - setKeyClass(realKey, t) - } + } else setKeyClass(realKey, t) } } @@ -82,7 +88,7 @@ object DataStoreHelper { R.drawable.profile_bg_pink, R.drawable.profile_bg_purple, R.drawable.profile_bg_red, - R.drawable.profile_bg_teal + R.drawable.profile_bg_teal, ) private var searchPreferenceProvidersStrings: List by UserPreferenceDelegate( @@ -112,16 +118,17 @@ object DataStoreHelper { private var searchPreferenceTagsStrings: List by UserPreferenceDelegate( "search_pref_tags", listOf(TvType.Movie, TvType.TvSeries).map { it.name }) + var searchPreferenceTags: List get() = deserializeTv(searchPreferenceTagsStrings) set(value) { searchPreferenceTagsStrings = serializeTv(value) } - private var homePreferenceStrings: List by UserPreferenceDelegate( "home_pref_homepage", listOf(TvType.Movie, TvType.TvSeries).map { it.name }) + var homePreference: List get() = deserializeTv(homePreferenceStrings) set(value) { @@ -132,38 +139,38 @@ object DataStoreHelper { "home_bookmarked_last_list", IntArray(0) ) + var playBackSpeed: Float by UserPreferenceDelegate("playback_speed", 1.0f) var resizeMode: Int by UserPreferenceDelegate("resize_mode", 0) var librarySortingMode: Int by UserPreferenceDelegate( "library_sorting_mode", ListSorting.AlphabeticalA.ordinal ) + private var _resultsSortingMode: Int by UserPreferenceDelegate( "results_sorting_mode", EpisodeSortType.NUMBER_ASC.ordinal ) + var resultsSortingMode: EpisodeSortType get() = EpisodeSortType.entries.getOrNull(_resultsSortingMode) ?: EpisodeSortType.NUMBER_ASC set(value) { _resultsSortingMode = value.ordinal } + @Serializable data class Account( - @JsonProperty("keyIndex") - val keyIndex: Int, - @JsonProperty("name") - val name: String, - @JsonProperty("customImage") - val customImage: String? = null, - @JsonProperty("defaultImageIndex") - val defaultImageIndex: Int, - @JsonProperty("lockPin") - val lockPin: String? = null, + @JsonProperty("keyIndex") @SerialName("keyIndex") val keyIndex: Int, + @JsonProperty("name") @SerialName("name") val name: String, + @JsonProperty("customImage") @SerialName("customImage") val customImage: String? = null, + @JsonProperty("defaultImageIndex") @SerialName("defaultImageIndex") val defaultImageIndex: Int, + @JsonProperty("lockPin") @SerialName("lockPin") val lockPin: String? = null, ) { - val image - get() = customImage?.let { UiImage.Image(it) } ?: profileImages.getOrNull( - defaultImageIndex - )?.let { UiImage.Drawable(it) } ?: UiImage.Drawable(profileImages.first()) + @get:JsonIgnore + val image get() = customImage?.let { UiImage.Image(it) } ?: + profileImages.getOrNull(defaultImageIndex)?.let { + UiImage.Drawable(it) + } ?: UiImage.Drawable(profileImages.first()) } const val TAG = "data_store_helper" @@ -181,14 +188,11 @@ object DataStoreHelper { val key = "$currentAccount/$USER_SELECTED_HOMEPAGE_API" if (value == null) { removeKey(key) - } else { - setKey(key, value) - } + } else setKey(key, value) } fun setAccount(account: Account) { val homepage = currentHomePage - selectedKeyIndex = account.keyIndex AccountManager.updateAccountIds() showToast(context?.getString(R.string.logged_account, account.name) ?: account.name) @@ -206,7 +210,7 @@ object DataStoreHelper { currentAccounts.getOrNull(currentAccounts.indexOfFirst { it.keyIndex == 0 }) ?: Account( keyIndex = 0, name = context.getString(R.string.default_account), - defaultImageIndex = 0 + defaultImageIndex = 0, ) } } @@ -226,24 +230,25 @@ object DataStoreHelper { } ?: accounts.toList()).firstNotNullOfOrNull { account -> if (account.keyIndex == selectedKeyIndex) { account - } else { - null - } + } else null } } + @Serializable data class PosDur( - @JsonProperty("position") val position: Long, - @JsonProperty("duration") val duration: Long + @JsonProperty("position") @SerialName("position") val position: Long, + @JsonProperty("duration") @SerialName("duration") val duration: Long, ) fun PosDur.fixVisual(): PosDur { if (duration <= 0) return PosDur(0, duration) val percentage = position * 100 / duration - if (percentage <= 1) return PosDur(0, duration) - if (percentage <= 5) return PosDur(5 * duration / 100, duration) - if (percentage >= 95) return PosDur(duration, duration) - return this + return when { + percentage <= 1 -> PosDur(0, duration) + percentage <= 5 -> PosDur(5 * duration / 100, duration) + percentage >= 95 -> PosDur(duration, duration) + else -> this + } } fun Int.toYear(): Date = @@ -251,28 +256,30 @@ object DataStoreHelper { /** * Used to display notifications on new episodes and posters in library. - **/ + */ + @Serializable abstract class LibrarySearchResponse( - @JsonProperty("id") override var id: Int?, - @JsonProperty("latestUpdatedTime") open val latestUpdatedTime: Long, - @JsonProperty("name") override val name: String, - @JsonProperty("url") override val url: String, - @JsonProperty("apiName") override val apiName: String, - @JsonProperty("type") override var type: TvType?, - @JsonProperty("posterUrl") override var posterUrl: String?, - @JsonProperty("year") open val year: Int?, - @JsonProperty("syncData") open val syncData: Map?, - @JsonProperty("quality") override var quality: SearchQuality?, - @JsonProperty("posterHeaders") override var posterHeaders: Map?, - @JsonProperty("plot") open val plot: String? = null, - @JsonProperty("score") override var score: Score? = null, - @JsonProperty("tags") open val tags: List? = null, + @Transient override var id: Int? = null, + @Transient open val latestUpdatedTime: Long = 0L, + @Transient override val name: String = "", + @Transient override val url: String = "", + @Transient override val apiName: String = "", + @Transient override var type: TvType? = null, + @Transient override var posterUrl: String? = null, + @Transient open val year: Int? = null, + @Transient open val syncData: Map? = null, + @Transient override var quality: SearchQuality? = null, + @Transient override var posterHeaders: Map? = null, + @Transient open val plot: String? = null, + @Transient override var score: Score? = null, + @Transient open val tags: List? = null, ) : SearchResponse { @JsonProperty("rating", access = JsonProperty.Access.WRITE_ONLY) + @SerialName("rating") @Deprecated( "`rating` is the old scoring system, use score instead", replaceWith = ReplaceWith("score"), - level = DeprecationLevel.ERROR + level = DeprecationLevel.ERROR, ) var rating: Int? = null set(value) { @@ -283,23 +290,26 @@ object DataStoreHelper { } } + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = SubscribedData.Serializer::class) data class SubscribedData( - @JsonProperty("subscribedTime") val subscribedTime: Long, - @JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map, - override var id: Int?, - override val latestUpdatedTime: Long, - override val name: String, - override val url: String, - override val apiName: String, - override var type: TvType?, - override var posterUrl: String?, - override val year: Int?, - override val syncData: Map? = null, - override var quality: SearchQuality? = null, - override var posterHeaders: Map? = null, - override val plot: String? = null, - override var score: Score? = null, - override val tags: List? = null, + @JsonProperty("subscribedTime") @SerialName("subscribedTime") val subscribedTime: Long, + @JsonProperty("lastSeenEpisodeCount") @SerialName("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map, + @JsonProperty("id") @SerialName("id") override var id: Int?, + @JsonProperty("latestUpdatedTime") @SerialName("latestUpdatedTime") override val latestUpdatedTime: Long, + @JsonProperty("name") @SerialName("name") override val name: String, + @JsonProperty("url") @SerialName("url") override val url: String, + @JsonProperty("apiName") @SerialName("apiName") override val apiName: String, + @JsonProperty("type") @SerialName("type") override var type: TvType?, + @JsonProperty("posterUrl") @SerialName("posterUrl") override var posterUrl: String?, + @JsonProperty("year") @SerialName("year") override val year: Int?, + @JsonProperty("syncData") @SerialName("syncData") override val syncData: Map? = null, + @JsonProperty("quality") @SerialName("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") @SerialName("posterHeaders") override var posterHeaders: Map? = null, + @JsonProperty("plot") @SerialName("plot") override val plot: String? = null, + @JsonProperty("score") @SerialName("score") override var score: Score? = null, + @JsonProperty("tags") @SerialName("tags") override val tags: List? = null, ) : LibrarySearchResponse( id, latestUpdatedTime, @@ -314,8 +324,13 @@ object DataStoreHelper { posterHeaders, plot, score, - tags + tags, ) { + object Serializer : WriteOnlySerializer( + SubscribedData.generatedSerializer(), + setOf("rating"), + ) + fun toLibraryItem(): SyncAPI.LibraryItem? { return SyncAPI.LibraryItem( name, @@ -334,27 +349,30 @@ object DataStoreHelper { this.id, plot = this.plot, score = this.score, - tags = this.tags + tags = this.tags, ) } } + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = BookmarkedData.Serializer::class) data class BookmarkedData( - @JsonProperty("bookmarkedTime") val bookmarkedTime: Long, - override var id: Int?, - override val latestUpdatedTime: Long, - override val name: String, - override val url: String, - override val apiName: String, - override var type: TvType?, - override var posterUrl: String?, - override val year: Int?, - override val syncData: Map? = null, - override var quality: SearchQuality? = null, - override var posterHeaders: Map? = null, - override val plot: String? = null, - override var score: Score? = null, - override val tags: List? = null, + @JsonProperty("bookmarkedTime") @SerialName("bookmarkedTime") val bookmarkedTime: Long, + @JsonProperty("id") @SerialName("id") override var id: Int?, + @JsonProperty("latestUpdatedTime") @SerialName("latestUpdatedTime") override val latestUpdatedTime: Long, + @JsonProperty("name") @SerialName("name") override val name: String, + @JsonProperty("url") @SerialName("url") override val url: String, + @JsonProperty("apiName") @SerialName("apiName") override val apiName: String, + @JsonProperty("type") @SerialName("type") override var type: TvType?, + @JsonProperty("posterUrl") @SerialName("posterUrl") override var posterUrl: String?, + @JsonProperty("year") @SerialName("year") override val year: Int?, + @JsonProperty("syncData") @SerialName("syncData") override val syncData: Map? = null, + @JsonProperty("quality") @SerialName("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") @SerialName("posterHeaders") override var posterHeaders: Map? = null, + @JsonProperty("plot") @SerialName("plot") override val plot: String? = null, + @JsonProperty("score") @SerialName("score") override var score: Score? = null, + @JsonProperty("tags") @SerialName("tags") override val tags: List? = null, ) : LibrarySearchResponse( id, latestUpdatedTime, @@ -367,8 +385,13 @@ object DataStoreHelper { syncData, quality, posterHeaders, - plot + plot, ) { + object Serializer : WriteOnlySerializer( + BookmarkedData.generatedSerializer(), + setOf("rating"), + ) + fun toLibraryItem(id: String): SyncAPI.LibraryItem { return SyncAPI.LibraryItem( name, @@ -387,27 +410,30 @@ object DataStoreHelper { this.id, plot = this.plot, score = this.score, - tags = this.tags + tags = this.tags, ) } } + @OptIn(ExperimentalSerializationApi::class) // KeepGeneratedSerializer is an experimental annotation for now + @KeepGeneratedSerializer + @Serializable(with = FavoritesData.Serializer::class) data class FavoritesData( - @JsonProperty("favoritesTime") val favoritesTime: Long, - override var id: Int?, - override val latestUpdatedTime: Long, - override val name: String, - override val url: String, - override val apiName: String, - override var type: TvType?, - override var posterUrl: String?, - override val year: Int?, - override val syncData: Map? = null, - override var quality: SearchQuality? = null, - override var posterHeaders: Map? = null, - override val plot: String? = null, - override var score: Score? = null, - override val tags: List? = null, + @JsonProperty("favoritesTime") @SerialName("favoritesTime") val favoritesTime: Long, + @JsonProperty("id") @SerialName("id") override var id: Int?, + @JsonProperty("latestUpdatedTime") @SerialName("latestUpdatedTime") override val latestUpdatedTime: Long, + @JsonProperty("name") @SerialName("name") override val name: String, + @JsonProperty("url") @SerialName("url") override val url: String, + @JsonProperty("apiName") @SerialName("apiName") override val apiName: String, + @JsonProperty("type") @SerialName("type") override var type: TvType?, + @JsonProperty("posterUrl") @SerialName("posterUrl") override var posterUrl: String?, + @JsonProperty("year") @SerialName("year") override val year: Int?, + @JsonProperty("syncData") @SerialName("syncData") override val syncData: Map? = null, + @JsonProperty("quality") @SerialName("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") @SerialName("posterHeaders") override var posterHeaders: Map? = null, + @JsonProperty("plot") @SerialName("plot") override val plot: String? = null, + @JsonProperty("score") @SerialName("score") override var score: Score? = null, + @JsonProperty("tags") @SerialName("tags") override val tags: List? = null, ) : LibrarySearchResponse( id, latestUpdatedTime, @@ -420,8 +446,13 @@ object DataStoreHelper { syncData, quality, posterHeaders, - plot + plot, ) { + object Serializer : WriteOnlySerializer( + FavoritesData.generatedSerializer(), + setOf("rating"), + ) + fun toLibraryItem(): SyncAPI.LibraryItem? { return SyncAPI.LibraryItem( name, @@ -440,31 +471,32 @@ object DataStoreHelper { this.id, plot = this.plot, score = this.score, - tags = this.tags + tags = this.tags, ) } } + @Serializable data class ResumeWatchingResult( - @JsonProperty("name") override val name: String, - @JsonProperty("url") override val url: String, - @JsonProperty("apiName") override val apiName: String, - @JsonProperty("type") override var type: TvType? = null, - @JsonProperty("posterUrl") override var posterUrl: String?, - @JsonProperty("watchPos") val watchPos: PosDur?, - @JsonProperty("id") override var id: Int?, - @JsonProperty("parentId") val parentId: Int?, - @JsonProperty("episode") val episode: Int?, - @JsonProperty("season") val season: Int?, - @JsonProperty("isFromDownload") val isFromDownload: Boolean, - @JsonProperty("quality") override var quality: SearchQuality? = null, - @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, - @JsonProperty("score") override var score: Score? = null, + @JsonProperty("name") @SerialName("name") override val name: String, + @JsonProperty("url") @SerialName("url") override val url: String, + @JsonProperty("apiName") @SerialName("apiName") override val apiName: String, + @JsonProperty("type") @SerialName("type") override var type: TvType? = null, + @JsonProperty("posterUrl") @SerialName("posterUrl") override var posterUrl: String?, + @JsonProperty("watchPos") @SerialName("watchPos") val watchPos: PosDur?, + @JsonProperty("id") @SerialName("id") override var id: Int?, + @JsonProperty("parentId") @SerialName("parentId") val parentId: Int?, + @JsonProperty("episode") @SerialName("episode") val episode: Int?, + @JsonProperty("season") @SerialName("season") val season: Int?, + @JsonProperty("isFromDownload") @SerialName("isFromDownload") val isFromDownload: Boolean, + @JsonProperty("quality") @SerialName("quality") override var quality: SearchQuality? = null, + @JsonProperty("posterHeaders") @SerialName("posterHeaders") override var posterHeaders: Map? = null, + @JsonProperty("score") @SerialName("score") override var score: Score? = null, ) : SearchResponse /** * A datastore wide account for future implementations of a multiple account system - **/ + */ fun getAllWatchStateIds(): List? { val folder = "$currentAccount/$RESULT_WATCH_STATE" @@ -500,7 +532,7 @@ object DataStoreHelper { } fun migrateResumeWatching() { - // if (getKey(RESULT_RESUME_WATCHING_HAS_MIGRATED, false) != true) { + // if (getKey(RESULT_RESUME_WATCHING_HAS_MIGRATED, false) != true) { setKey(RESULT_RESUME_WATCHING_HAS_MIGRATED, true) getAllResumeStateIdsOld()?.forEach { id -> getLastWatchedOld(id)?.let { @@ -510,12 +542,12 @@ object DataStoreHelper { it.episode, it.season, it.isFromDownload, - it.updateTime + it.updateTime, ) removeLastWatchedOld(it.parentId) } } - //} + // } } fun setLastWatched( @@ -536,7 +568,7 @@ object DataStoreHelper { episode, season, updateTime ?: System.currentTimeMillis(), - isFromDownload + isFromDownload, ) ) } @@ -553,7 +585,7 @@ object DataStoreHelper { fun getLastWatched(id: Int?): DownloadObjects.ResumeWatching? { if (id == null) return null - return getKey( + return getKey( "$currentAccount/$RESULT_RESUME_WATCHING", id.toString(), ) @@ -561,7 +593,7 @@ object DataStoreHelper { private fun getLastWatchedOld(id: Int?): DownloadObjects.ResumeWatching? { if (id == null) return null - return getKey( + return getKey( "$currentAccount/$RESULT_RESUME_WATCHING_OLD", id.toString(), ) @@ -575,18 +607,18 @@ object DataStoreHelper { fun getBookmarkedData(id: Int?): BookmarkedData? { if (id == null) return null - return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) + return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) } fun getAllBookmarkedData(): List { return getKeys("$currentAccount/$RESULT_WATCH_STATE_DATA")?.mapNotNull { - getKey(it) + getKey(it) } ?: emptyList() } fun getAllSubscriptions(): List { return getKeys("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA")?.mapNotNull { - getKey(it) + getKey(it) } ?: emptyList() } @@ -598,12 +630,12 @@ object DataStoreHelper { /** * Set new seen episodes and update time - **/ + */ fun updateSubscribedData(id: Int?, data: SubscribedData?, episodeResponse: EpisodeResponse?) { if (id == null || data == null || episodeResponse == null) return val newData = data.copy( latestUpdatedTime = unixTimeMS, - lastSeenEpisodeCount = episodeResponse.getLatestEpisodes() + lastSeenEpisodeCount = episodeResponse.getLatestEpisodes(), ) setKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString(), newData) } @@ -616,12 +648,12 @@ object DataStoreHelper { fun getSubscribedData(id: Int?): SubscribedData? { if (id == null) return null - return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) + return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) } fun getAllFavorites(): List { return getKeys("$currentAccount/$RESULT_FAVORITES_STATE_DATA")?.mapNotNull { - getKey(it) + getKey(it) } ?: emptyList() } @@ -639,7 +671,7 @@ object DataStoreHelper { fun getFavoritesData(id: Int?): FavoritesData? { if (id == null) return null - return getKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString()) + return getKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString()) } fun setViewPos(id: Int?, pos: Long, dur: Long) { @@ -648,10 +680,10 @@ object DataStoreHelper { setKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), PosDur(pos, dur)) } - /** Sets the position, duration, and resume data of an episode/movie, - * - * if nextEpisode is not specified it will not be able to set the next episode as resumable if progress > NEXT_WATCH_EPISODE_PERCENTAGE - * */ + /** + * Sets the position, duration, and resume data of an episode/movie, + * If nextEpisode is not specified it will not be able to set the next episode as resumable if progress > NEXT_WATCH_EPISODE_PERCENTAGE + */ fun setViewPosAndResume(id: Int?, position: Long, duration: Long, currentEpisode: Any?, nextEpisode: Any?) { setViewPos(id, position, duration) if (id != null) { @@ -687,7 +719,7 @@ object DataStoreHelper { resumeMeta.id, resumeMeta.episode, resumeMeta.season, - isFromDownload = false + isFromDownload = false, ) } @@ -697,7 +729,7 @@ object DataStoreHelper { resumeMeta.id, resumeMeta.episode, resumeMeta.season, - isFromDownload = true + isFromDownload = true, ) } } @@ -706,17 +738,16 @@ object DataStoreHelper { fun getViewPos(id: Int?): PosDur? { if (id == null) return null - return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) + return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) } fun getVideoWatchState(id: Int?): VideoWatchState? { if (id == null) return null - return getKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString(), null) + return getKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString(), null) } fun setVideoWatchState(id: Int?, watchState: VideoWatchState) { if (id == null) return - // None == No key if (watchState == VideoWatchState.None) { removeKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString()) @@ -727,7 +758,7 @@ object DataStoreHelper { fun getDub(id: Int): DubStatus? { return DubStatus.entries - .getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1) + .getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1) } fun setDub(id: Int, status: DubStatus) { @@ -748,13 +779,13 @@ object DataStoreHelper { getKey( "$currentAccount/$RESULT_WATCH_STATE", id.toString(), - null + null, ) ) } fun getResultSeason(id: Int): Int? { - return getKey("$currentAccount/$RESULT_SEASON", id.toString(), null) + return getKey("$currentAccount/$RESULT_SEASON", id.toString(), null) } fun setResultSeason(id: Int, value: Int?) { @@ -762,7 +793,7 @@ object DataStoreHelper { } fun getResultEpisode(id: Int): Int? { - return getKey("$currentAccount/$RESULT_EPISODE", id.toString(), null) + return getKey("$currentAccount/$RESULT_EPISODE", id.toString(), null) } fun setResultEpisode(id: Int, value: Int?) { @@ -775,12 +806,11 @@ object DataStoreHelper { fun getSync(id: Int, idPrefixes: List): List { return idPrefixes.map { idPrefix -> - getKey("${idPrefix}_sync", id.toString()) + getKey("${idPrefix}_sync", id.toString()) } } var pinnedProviders: Array - get() = getKey(USER_PINNED_PROVIDERS) ?: emptyArray() + get() = getKey>(USER_PINNED_PROVIDERS) ?: emptyArray() set(value) = setKey(USER_PINNED_PROVIDERS, value) - } From 288dda18670083ef433f1f072017bb8a257ae40b Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:27:02 -0600 Subject: [PATCH 2/4] Readd brackets --- .../com/lagradost/cloudstream3/utils/DataStoreHelper.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index d134b9b4384..fca4026021c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -75,7 +75,9 @@ class UserPreferenceDelegate( ) { if (t == null) { removeKey(realKey) - } else setKeyClass(realKey, t) + } else { + setKeyClass(realKey, t) + } } } @@ -188,7 +190,9 @@ object DataStoreHelper { val key = "$currentAccount/$USER_SELECTED_HOMEPAGE_API" if (value == null) { removeKey(key) - } else setKey(key, value) + } else { + setKey(key, value) + } } fun setAccount(account: Account) { From 7b960a3a6201e682e1fd75fa9599f49070583532 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:45:06 +0000 Subject: [PATCH 3/4] Update DataStoreHelper.kt --- .../java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index fca4026021c..260c30dd2cd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -234,7 +234,9 @@ object DataStoreHelper { } ?: accounts.toList()).firstNotNullOfOrNull { account -> if (account.keyIndex == selectedKeyIndex) { account - } else null + } else { + null + } } } From d64e2bc43c51fcb7ae7db201a2730cf8c05b71ab Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:57:32 -0600 Subject: [PATCH 4/4] Add comment --- .../com/lagradost/cloudstream3/utils/DataStoreHelper.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 260c30dd2cd..ec896facb65 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -265,6 +265,14 @@ object DataStoreHelper { */ @Serializable abstract class LibrarySearchResponse( + /** + * These fields are marked @Transient because this class is only ever serialized through + * through its subclasses, which redeclare each property with their own @SerialName + * annotations. Without @Transient here, kotlinx.serialization would try to + * generate a serializer for the abstract base class itself (or double-serialize + * these fields), which fails/conflicts since these are meant to be overridden, + * not serialized directly from the parent. + */ @Transient override var id: Int? = null, @Transient open val latestUpdatedTime: Long = 0L, @Transient override val name: String = "",