From 8b6d0ec8c48ba3b25d57eb70bf079c4a71ab95a2 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 19 Jul 2025 16:09:50 +0000 Subject: [PATCH 001/618] Added new class Score (#1748) --- .../cloudstream3/syncproviders/SyncApi.kt | 7 +- .../syncproviders/providers/AniListApi.kt | 2 +- .../syncproviders/providers/MALApi.kt | 3 +- .../cloudstream3/ui/result/EpisodeAdapter.kt | 8 +- .../cloudstream3/ui/result/ResultFragment.kt | 5 +- .../ui/result/ResultFragmentPhone.kt | 4 +- .../ui/result/ResultViewModel2.kt | 39 +- .../cloudstream3/ui/search/SearchHelper.kt | 2 +- .../cloudstream3/utils/DataStoreHelper.kt | 160 ++++++-- .../cloudstream3/utils/VideoDownloadHelper.kt | 18 +- app/src/main/res/values-hr/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values-zh/strings.xml | 1 - app/src/main/res/values/strings.xml | 2 +- .../com/lagradost/cloudstream3/MainAPI.kt | 376 +++++++++++++++--- .../cloudstream3/metaproviders/MyDramaList.kt | 5 +- .../metaproviders/TmdbProvider.kt | 4 +- .../metaproviders/TraktProvider.kt | 6 +- 19 files changed, 512 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncApi.kt index 9d43685c830..478960e53f0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncApi.kt @@ -80,8 +80,7 @@ interface SyncAPI : OAuth2API { var totalEpisodes: Int? = null, var title: String? = null, - /**1-1000*/ - var publicScore: Int? = null, + var publicScore: Score? = null, /**In minutes*/ var duration: Int? = null, var synopsis: String? = null, @@ -154,7 +153,7 @@ interface SyncAPI : OAuth2API { val episodesCompleted: Int?, val episodesTotal: Int?, /** Out of 100 */ - val personalRating: Int?, + val personalRating: Int?, // TODO also update this to Score val lastUpdatedUnixTime: Long?, override val apiName: String, override var type: TvType?, @@ -164,7 +163,7 @@ interface SyncAPI : OAuth2API { val releaseDate: Date?, override var id: Int? = null, val plot : String? = null, - val rating: Int? = null, + val rating: Score? = null, val tags: List? = null ) : SearchResponse } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index f8e82409522..0b09c5504e1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -141,7 +141,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } ) }, - publicScore = season.averageScore?.times(100), + publicScore = Score.from100(season.averageScore), recommendations = season.recommendations?.edges?.mapNotNull { rec -> val recMedia = rec.node.mediaRecommendation SyncAPI.SyncSearchResult( diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index 4836eca131d..2cecf862d25 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -8,6 +8,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.ShowStatus import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.app @@ -214,7 +215,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { id = internalId.toString(), totalEpisodes = malAnime.numEpisodes, title = malAnime.title, - publicScore = malAnime.mean?.toFloat()?.times(1000)?.toInt(), + publicScore = Score.from10(malAnime.mean), duration = malAnime.averageEpisodeDuration, synopsis = malAnime.synopsis, airStatus = when (malAnime.status) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 1e0438cdd71..565c4240d49 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -188,7 +188,7 @@ class EpisodeAdapter( season = card.season, id = card.id, parentId = card.parentId, - rating = card.rating, + score = card.score, description = card.description, cacheTime = System.currentTimeMillis(), ), null @@ -230,9 +230,9 @@ class EpisodeAdapter( episodePoster.loadImage(card.poster) - if (card.rating != null) { + if (card.score != null) { episodeRating.text = episodeRating.context?.getString(R.string.rated_format) - ?.format(card.rating.toFloat() / 10f) + ?.format(card.score.toFloat(10)) // TODO Change rated_format to use card.score.toString() } else { episodeRating.text = "" } @@ -347,7 +347,7 @@ class EpisodeAdapter( season = card.season, id = card.id, parentId = card.parentId, - rating = card.rating, + score = card.score, description = card.description, cacheTime = System.currentTimeMillis(), ), null diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index c12f011831b..f0d6a5087fa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -5,6 +5,7 @@ import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction @@ -39,7 +40,7 @@ data class ResultEpisode( val index: Int, val position: Long, // time in MS val duration: Long, // duration in MS - val rating: Int?, + val score: Score?, val description: String?, val isFiller: Boolean?, val tvType: TvType, @@ -81,7 +82,7 @@ fun buildResultEpisode( apiName: String, id: Int, index: Int, - rating: Int? = null, + rating: Score? = null, description: String? = null, isFiller: Boolean? = null, tvType: TvType, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index 3d992d87331..897b81e58bf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -696,8 +696,8 @@ open class ResultFragmentPhone : FullScreenPlayer() { season = null, id = ep.id, parentId = ep.id, - rating = null, - description = null, + score = ep.score, + description = ep.description, cacheTime = System.currentTimeMillis(), ), null diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 8641c62be45..86cd8e668c1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -264,8 +264,8 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { ), yearText = txt(year?.toString()), apiName = txt(apiName), - ratingText = rating?.div(1000f) - ?.let { if (it <= 0.1f) null else txt(R.string.rating_format, it) }, + ratingText = score?.toStringNull(0.1, 10, 1, false, '.') + ?.let { txt(R.string.rating_format, it) }, contentRatingText = txt(contentRating), vpnText = txt( when (repo.vpnStatus) { @@ -738,7 +738,7 @@ class ResultViewModel2 : ViewModel() { season = episode.season, id = episode.id, parentId = parentId, - rating = episode.rating, + score = episode.score, description = episode.description, cacheTime = System.currentTimeMillis(), ) @@ -925,7 +925,7 @@ class ResultViewModel2 : ViewModel() { response.syncData, plot = response.plot, tags = response.tags, - rating = response.rating + score = response.score ) ) } @@ -1022,7 +1022,7 @@ class ResultViewModel2 : ViewModel() { response.year, response.syncData, plot = response.plot, - rating = response.rating, + score = response.score, tags = response.tags ) ) @@ -1093,7 +1093,7 @@ class ResultViewModel2 : ViewModel() { response.year, response.syncData, plot = response.plot, - rating = response.rating, + score = response.score, tags = response.tags ) ) @@ -1404,7 +1404,7 @@ class ResultViewModel2 : ViewModel() { }, isCasting = isCasting ) - } catch (e : CancellationException) { + } catch (e: CancellationException) { // Do nothing } catch (e: Exception) { logError(e) @@ -1660,13 +1660,14 @@ class ResultViewModel2 : ViewModel() { // Show player selection dialog val players = VideoClickActionHolder.getPlayers(ctx) val options = mutableListOf>() - + // Add internal player option options.add(txt(R.string.episode_action_play_in_app) to ACTION_PLAY_EPISODE_IN_PLAYER) - + // Add external player options options.addAll(players.filter { it !is AlwaysAskAction }.map { player -> - player.name to (VideoClickActionHolder.uniqueIdToId(player.uniqueId()) ?: ACTION_PLAY_EPISODE_IN_PLAYER) + player.name to (VideoClickActionHolder.uniqueIdToId(player.uniqueId()) + ?: ACTION_PLAY_EPISODE_IN_PLAYER) }) postPopup( @@ -1723,7 +1724,7 @@ class ResultViewModel2 : ViewModel() { if (meta != null) { duration = duration ?: meta.duration - rating = rating ?: meta.publicScore + score = score ?: meta.publicScore tags = tags ?: meta.genres plot = if (plot.isNullOrBlank()) meta.synopsis else plot posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl @@ -1942,8 +1943,8 @@ class ResultViewModel2 : ViewModel() { return when (sorting) { EpisodeSortType.NUMBER_ASC -> episodes.sortedBy { it.episode } EpisodeSortType.NUMBER_DESC -> episodes.sortedByDescending { it.episode } - EpisodeSortType.RATING_HIGH_LOW -> episodes.sortedByDescending { it.rating ?: 0 } - EpisodeSortType.RATING_LOW_HIGH -> episodes.sortedBy { it.rating ?: 0 } + EpisodeSortType.RATING_HIGH_LOW -> episodes.sortedByDescending { it.score?.toDouble() ?: 0.0 } + EpisodeSortType.RATING_LOW_HIGH -> episodes.sortedBy { it.score?.toDouble() ?: 0.0 } EpisodeSortType.DATE_NEWEST -> episodes.sortedByDescending { it.airDate } EpisodeSortType.DATE_OLDEST -> episodes.sortedBy { it.airDate } } @@ -2022,7 +2023,7 @@ class ResultViewModel2 : ViewModel() { return when (type) { EpisodeSortType.NUMBER_ASC, EpisodeSortType.NUMBER_DESC -> true EpisodeSortType.RATING_HIGH_LOW, EpisodeSortType.RATING_LOW_HIGH -> - episodes.any { it.rating != null } + episodes.any { it.score != null } EpisodeSortType.DATE_NEWEST, EpisodeSortType.DATE_OLDEST -> episodes.any { it.airDate != null } @@ -2274,7 +2275,7 @@ class ResultViewModel2 : ViewModel() { loadResponse.apiName, id, index, - i.rating, + i.score, i.description, fillers.getOrDefault(episode, false), loadResponse.type, @@ -2330,7 +2331,7 @@ class ResultViewModel2 : ViewModel() { loadResponse.apiName, id, index, - episode.rating, + episode.score, episode.description, null, loadResponse.type, @@ -2620,7 +2621,7 @@ class ResultViewModel2 : ViewModel() { override var posterUrl: String?, override var year: Int? = null, override var plot: String? = null, - override var rating: Int? = null, + override var score: Score? = null, override var tags: List? = null, override var duration: Int? = null, override var trailers: MutableList = mutableListOf(), @@ -2654,12 +2655,12 @@ class ResultViewModel2 : ViewModel() { ).apply { if (searchResponse is SyncAPI.LibraryItem) { this.plot = searchResponse.plot - this.rating = searchResponse.personalRating?.times(100) ?: searchResponse.rating + this.score = Score.from100(searchResponse.personalRating) ?: searchResponse.rating this.tags = searchResponse.tags } if (searchResponse is DataStoreHelper.BookmarkedData) { this.plot = searchResponse.plot - this.rating = searchResponse.rating + this.score = searchResponse.score this.tags = searchResponse.tags } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt index 01ec17b63f4..e176d6c9b6d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt @@ -38,7 +38,7 @@ object SearchHelper { season = card.season, id = id, parentId = card.parentId ?: return, - rating = null, + score = null, description = null, cacheTime = System.currentTimeMillis(), ) 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 650502dcaba..0fad6010280 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.EpisodeResponse import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.SearchQuality import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType @@ -79,50 +80,64 @@ object DataStoreHelper { R.drawable.profile_bg_teal ) - private var searchPreferenceProvidersStrings : List by UserPreferenceDelegate( + private var searchPreferenceProvidersStrings: List by UserPreferenceDelegate( /** java moment right here, as listOf()::class.java != List(0) { "" }::class.java */ "search_pref_providers", List(0) { "" } ) - private fun serializeTv(data : List) : List = data.map { it.name } + private fun serializeTv(data: List): List = data.map { it.name } - private fun deserializeTv(data : List) : List { + private fun deserializeTv(data: List): List { return data.mapNotNull { listName -> TvType.values().firstOrNull { it.name == listName } } } - var searchPreferenceProviders : List + var searchPreferenceProviders: List get() { val ret = searchPreferenceProvidersStrings return ret.ifEmpty { context?.filterProviderByPreferredMedia()?.map { it.name } ?: emptyList() } - } set(value) { + } + set(value) { searchPreferenceProvidersStrings = value } - private var searchPreferenceTagsStrings : List by UserPreferenceDelegate("search_pref_tags", listOf(TvType.Movie, TvType.TvSeries).map { it.name }) - var searchPreferenceTags : List + 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 + 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) { homePreferenceStrings = serializeTv(value) } - var homeBookmarkedList : IntArray by UserPreferenceDelegate("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 + var homeBookmarkedList: IntArray by UserPreferenceDelegate( + "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 @@ -140,7 +155,10 @@ object DataStoreHelper { @JsonProperty("lockPin") val lockPin: String? = null, ) { - val image get() = customImage?.let { UiImage.Image(it) } ?: profileImages.getOrNull(defaultImageIndex)?.let { UiImage.Drawable(it) } ?: UiImage.Drawable(profileImages.first()) + 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" @@ -222,7 +240,8 @@ object DataStoreHelper { return this } - fun Int.toYear() : Date = GregorianCalendar.getInstance().also { it.set(Calendar.YEAR, this) }.time + fun Int.toYear(): Date = + GregorianCalendar.getInstance().also { it.set(Calendar.YEAR, this) }.time /** * Used to display notifications on new episodes and posters in library. @@ -239,10 +258,23 @@ object DataStoreHelper { @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("rating") open val rating : Int? = null, - @JsonProperty("tags") open val tags : List? = null, - ) : SearchResponse + @JsonProperty("plot") open val plot: String? = null, + @JsonProperty("score") open var score: Score? = null, + @JsonProperty("tags") open val tags: List? = null, + ) : SearchResponse { + @JsonProperty("rating", access = JsonProperty.Access.WRITE_ONLY) + @Deprecated( + "`rating` is the old scoring system, use score instead", + replaceWith = ReplaceWith("score"), + level = DeprecationLevel.ERROR + ) + var rating: Int? = null + set(value) { + if (value != null) { + score = Score.fromOld(value) + } + } + } data class SubscribedData( @JsonProperty("subscribedTime") val subscribedTime: Long, @@ -259,9 +291,24 @@ object DataStoreHelper { override var quality: SearchQuality? = null, override var posterHeaders: Map? = null, override val plot: String? = null, - override val rating: Int? = null, + override var score: Score? = null, override val tags: List? = null, - ) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, syncData, quality, posterHeaders, plot,rating,tags) { + ) : LibrarySearchResponse( + id, + latestUpdatedTime, + name, + url, + apiName, + type, + posterUrl, + year, + syncData, + quality, + posterHeaders, + plot, + score, + tags + ) { fun toLibraryItem(): SyncAPI.LibraryItem? { return SyncAPI.LibraryItem( name, @@ -271,7 +318,16 @@ object DataStoreHelper { null, null, latestUpdatedTime, - apiName, type, posterUrl, posterHeaders, quality, year?.toYear(), this.id, plot = this.plot, rating = this.rating, tags = this.tags + apiName, + type, + posterUrl, + posterHeaders, + quality, + year?.toYear(), + this.id, + plot = this.plot, + rating = this.score, + tags = this.tags ) } } @@ -290,9 +346,22 @@ object DataStoreHelper { override var quality: SearchQuality? = null, override var posterHeaders: Map? = null, override val plot: String? = null, - override val rating: Int? = null, + override var score: Score? = null, override val tags: List? = null, - ) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, syncData, quality, posterHeaders, plot) { + ) : LibrarySearchResponse( + id, + latestUpdatedTime, + name, + url, + apiName, + type, + posterUrl, + year, + syncData, + quality, + posterHeaders, + plot + ) { fun toLibraryItem(id: String): SyncAPI.LibraryItem { return SyncAPI.LibraryItem( name, @@ -302,7 +371,16 @@ object DataStoreHelper { null, null, latestUpdatedTime, - apiName, type, posterUrl, posterHeaders, quality, year?.toYear(), this.id, plot = this.plot, rating = this.rating, tags = this.tags + apiName, + type, + posterUrl, + posterHeaders, + quality, + year?.toYear(), + this.id, + plot = this.plot, + rating = this.score, + tags = this.tags ) } } @@ -321,9 +399,22 @@ object DataStoreHelper { override var quality: SearchQuality? = null, override var posterHeaders: Map? = null, override val plot: String? = null, - override val rating: Int? = null, + override var score: Score? = null, override val tags: List? = null, - ) : LibrarySearchResponse(id, latestUpdatedTime, name, url, apiName, type, posterUrl, year, syncData, quality, posterHeaders,plot) { + ) : LibrarySearchResponse( + id, + latestUpdatedTime, + name, + url, + apiName, + type, + posterUrl, + year, + syncData, + quality, + posterHeaders, + plot + ) { fun toLibraryItem(): SyncAPI.LibraryItem? { return SyncAPI.LibraryItem( name, @@ -333,7 +424,16 @@ object DataStoreHelper { null, null, latestUpdatedTime, - apiName, type, posterUrl, posterHeaders, quality, year?.toYear(), this.id, plot = this.plot, rating = this.rating, tags = this.tags + apiName, + type, + posterUrl, + posterHeaders, + quality, + year?.toYear(), + this.id, + plot = this.plot, + rating = this.score, + tags = this.tags ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt index 30f66f835bb..966ccfd5662 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.utils import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.TvType object VideoDownloadHelper { abstract class DownloadCached( @@ -13,11 +14,24 @@ object VideoDownloadHelper { @JsonProperty("episode") val episode: Int, @JsonProperty("season") val season: Int?, @JsonProperty("parentId") val parentId: Int, - @JsonProperty("rating") val rating: Int?, + @JsonProperty("score") var score: Score? = null, @JsonProperty("description") val description: String?, @JsonProperty("cacheTime") val cacheTime: Long, override val id: Int, - ): DownloadCached(id) + ): DownloadCached(id) { + @JsonProperty("rating", access = JsonProperty.Access.WRITE_ONLY) + @Deprecated( + "`rating` is the old scoring system, use score instead", + replaceWith = ReplaceWith("score"), + level = DeprecationLevel.ERROR + ) + var rating: Int? = null + set(value) { + if (value != null) { + score = Score.fromOld(value) + } + } + } data class DownloadHeaderCached( @JsonProperty("apiName") val apiName: String, diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index d9afb2b1bde..1c0c5b577cb 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -9,7 +9,6 @@ -%d %d %d - %.1f/10.0 %d %1$s epizoda %2$d Glumačka postava: %s diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 05bf7d5a920..3a820d2d78d 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -9,7 +9,6 @@ -%d %d %d - %.1f/10.0 %d %1$s B. %2$d Cast: %s diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6b191d33fb7..e4d7f9fa289 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -9,7 +9,6 @@ -%d %d %d - %.1f/10.0 %d %1$s 共 %2$d 集 演員:%s diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index da1d7d29014..3ed225bb423 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -9,7 +9,6 @@ -%d %d %d - %.1f/10.0 %d %1$s 共 %2$d 集 演员:%s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a973dcc2105..0f2797d84c7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -84,7 +84,7 @@ -%d %d %d - %.1f/10.0 + %s/10.0 %d %1$s Ep %2$d Cast: %s diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index beb95403535..2e6887ab6b2 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -6,6 +6,7 @@ package com.lagradost.cloudstream3 +import com.fasterxml.jackson.annotation.JsonAutoDetect import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.json.JsonMapper @@ -28,6 +29,7 @@ import java.util.* import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.math.absoluteValue +import kotlin.math.roundToInt /** Api has not yet been published to stable, and will cause `NoSuchMethodException` on stable */ @MustBeDocumented // Same as java.lang.annotation.Documented @@ -490,35 +492,35 @@ abstract class MainAPI { * * Note that this is only a hint, and may not get respected if you request something too long. * */ - open val loadLinksTimeoutMs : Long? = null + open val loadLinksTimeoutMs: Long? = null /** * The timeout on the `getMainPage` functions in milliseconds. * * Note that this is only a hint, and may not get respected if you request something too long. * */ - open val getMainPageTimeoutMs : Long? = null + open val getMainPageTimeoutMs: Long? = null /** * The timeout on the `search` functions in milliseconds. * * Note that this is only a hint, and may not get respected if you request something too long. * */ - open val searchTimeoutMs : Long? = null + open val searchTimeoutMs: Long? = null /** * The timeout on the `quickSearch` functions in milliseconds. * * Note that this is only a hint, and may not get respected if you request something too long. * */ - open val quickSearchTimeoutMs : Long? = null + open val quickSearchTimeoutMs: Long? = null /** * The timeout on the `loadSearch` functions in milliseconds. * * Note that this is only a hint, and may not get respected if you request something too long. * */ - open val loadTimeoutMs : Long? = null + open val loadTimeoutMs: Long? = null /** @@ -770,6 +772,204 @@ enum class DubStatus(val id: Int) { Subbed(0), } +/** This is the primary way to store score/rating. Use Score.from or Score.from10 to parse the score + * as it does not have a public constructor. Use toInt/toFloat to get back the score. + * + * Internally it stores it as an int up to 10^9 to represent up to 10 significant digits. So think + * of this as a decimal class specifically for ratings. + * */ +@Prerelease +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) +class Score private constructor( + /** Decimal between [0, 10^9] representing the min score and max score respectively */ + @JsonProperty("data") + private val data: Int, +) { + fun toOld(): Int = toInt(10000) + + fun toByte(maxScore: Int): Byte = toLong(maxScore).toByte() + + fun toInt(maxScore: Int = 10): Int = toLong(maxScore).toInt() + + fun toLong(maxScore: Int = 10): Long = (data.toLong() * maxScore.toLong()) / MAX.toLong() + + fun toFloat(maxScore: Int = 10): Float = + (data.toFloat() / MAX.toFloat()) * maxScore.toFloat() + + fun toDouble(maxScore: Int = 10): Double = + (data.toDouble() / MAX.toDouble()) * maxScore.toDouble() + + override fun toString(): String = this.toString(10) + + /** Formats the rating to a human readable format (with no rounding) + * + * However it may also return null if the score is less than the minimum score, + * this is to avoid 0.0/10.0 in case of default = 0 + * */ + @Throws(IllegalArgumentException::class) + fun toStringNull( + minScore: Double, + maxScore: Int, + decimals: Int = 1, + removeTrailingZeros: Boolean = true, + decimalChar: Char = '.' + ): String? { + if (toDouble() < minScore) return null + return toString(maxScore, decimals, removeTrailingZeros, decimalChar) + } + + /** Formats the rating to a human readable format (with no rounding) */ + @Throws(IllegalArgumentException::class) + fun toString( + maxScore: Int, + decimals: Int = 1, + removeTrailingZeros: Boolean = true, + decimalChar: Char = '.' + ): String { + require(maxScore in 1..1000) { + "maxScore ∈ [1,1000]" + } + require(decimals in 0..MAX_ZEROS) { + "decimals ∈ [0,$MAX_ZEROS]" + } + var number = data.toLong() * maxScore.toLong() + val chars = CharArray(MAX_ZEROS + 6) + + for (i in chars.indices) { + chars[i] = (number % 10L).toInt().digitToChar() + number /= 10L + } + + var trailingZeros = 0 + for (i in chars.indices) { + if (chars[i] != '0') { + break + } + trailingZeros += 1 + } + + var leadingZeros = 0 + for (i in chars.indices.reversed()) { + if (chars[i] != '0') { + break + } + leadingZeros += 1 + } + + val stringBuilder = StringBuilder() + for (i in maxOf(MAX_ZEROS, (chars.size - leadingZeros - 1)) downTo MAX_ZEROS) { + stringBuilder.append(chars[i]) + } + + val end = if (removeTrailingZeros) { + maxOf(MAX_ZEROS - decimals, trailingZeros) + } else { + MAX_ZEROS - decimals + } + + if (end <= MAX_ZEROS - 1) { + stringBuilder.append(decimalChar) + for (i in MAX_ZEROS - 1 downTo end) { + stringBuilder.append(chars[i]) + } + } + + return stringBuilder.toString() + } + + companion object { + const val MAX: Int = 1000_000_000 + const val MIN: Int = 0 + const val MAX_ZEROS: Int = 9 + private const val TAG: String = "Score" + + fun fromOld(value: Int?): Score? { + if (value == null) return null + if (value < 0 || value > 10000) { + com.lagradost.api.Log.w(TAG, "old: $value ∉ [0, 10000]") + return null + } + return Score(value * 100_000) + } + + /** `value ∈ [0, maxScore]` */ + fun from(value: Int?, maxScore: Int): Score? { + if (value == null) { + return null + } + if (value < 0 || value > maxScore) { + com.lagradost.api.Log.w(TAG, "fromInt: $value ∉ [0, $maxScore]") + return null + } + return Score((MAX / maxScore) * value) + } + + /** `value ∈ [0.0, maxScore]` */ + fun from(value: Double?, maxScore: Int): Score? { + if (value == null) { + return null + } + if (value < 0.0 || value > maxScore) { + com.lagradost.api.Log.w(TAG, "fromDouble: $value ∉ [0.0, $maxScore]") + return null + } + return Score(((MAX / maxScore).toDouble() * value).roundToInt()) + } + + /** `value ∈ [0.0f, maxScore]` */ + fun from(value: Float?, maxScore: Int): Score? { + if (value == null) { + return null + } + if (value < 0.0 || value > maxScore) { + com.lagradost.api.Log.w(TAG, "fromFloat: $value ∉ [0.0f, $maxScore]") + return null + } + return Score(((MAX / maxScore).toFloat() * value).roundToInt()) + } + + /** `value ∈ ["0.0", maxScore]` */ + fun from(value: String?, maxScore: Int): Score? = + from(value?.trim()?.toDoubleOrNull()?.absoluteValue, maxScore) + + /** `value ∈ [0, 5]` */ + fun from5(value: Int?): Score? = from(value, 5) + + /** `value ∈ [0, 10]` */ + fun from10(value: Int?): Score? = from(value, 10) + + /** `value ∈ [0, 100]` */ + fun from100(value: Int?): Score? = from(value, 100) + + /** `value ∈ [0.0, 5.0]` */ + fun from5(value: Double?): Score? = from(value, 5) + + /** `value ∈ [0.0, 10.0]` */ + fun from10(value: Double?): Score? = from(value, 10) + + /** `value ∈ [0.0, 100.0]` */ + fun from100(value: Double?): Score? = from(value, 100) + + /** `value ∈ [0.0f, 5.0f]` */ + fun from5(value: Float?): Score? = from(value, 5) + + /** `value ∈ [0.0f, 10.0f]` */ + fun from10(value: Float?): Score? = from(value, 10) + + /** `value ∈ [0.0f, 100.0f]` */ + fun from100(value: Float?): Score? = from(value, 100) + + /** `value ∈ ["0.0", "5.0"]` */ + fun from5(value: String?): Score? = from(value, 5) + + /** `value ∈ ["0.0", "10.0"]` */ + fun from10(value: String?): Score? = from(value, 10) + + /** `value ∈ ["0.0", "100.0"]` */ + fun from100(value: String?): Score? = from(value, 100) + } +} + @Suppress("UNUSED_PARAMETER") enum class TvType(value: Int?) { Movie(1), @@ -1287,7 +1487,8 @@ data class TrailerData( * @property posterUrl Url of the media poster, appears on Top of result page. * @property year Year of the media, appears on result page. * @property plot Plot of the media, appears on result page. - * @property rating Rating of the media, appears on result page (0-10000). + * @property score Rating of the media, appears on result page. + * Use it with addScore or by assigning a score like `Score.from(string/float/int/double, 10)` or `Score.from10(string/float/int/double)` * @property tags Tags of the media, appears on result page. * @property duration duration of the media, appears on result page. * @property trailers list of the media [TrailerData], used to load trailers. @@ -1311,7 +1512,9 @@ interface LoadResponse { var posterUrl: String? var year: Int? var plot: String? - var rating: Int? // 0-10000 + + @Prerelease + var score: Score? var tags: List? var duration: Int? // in minutes var trailers: MutableList @@ -1325,7 +1528,17 @@ interface LoadResponse { var contentRating: String? @Prerelease - var uniqueUrl : String + var uniqueUrl: String + + @Deprecated( + "`rating` is the old scoring system, use score instead", + replaceWith = ReplaceWith("score") + ) + var rating: Int? + set(value) { + this.score = Score.fromOld(value) + } + get() = score?.toOld() companion object { var malIdPrefix = "" //malApi.idPrefix @@ -1541,15 +1754,21 @@ interface LoadResponse { this.addSimklId(SimklSyncServices.Tmdb, id) } + fun LoadResponse.addScore(score: String?, maxValue: Int = 10) { + this.score = Score.from(score, maxValue) + } + + fun LoadResponse.addScore(score: Score?) { + this.score = score + } + fun LoadResponse.addRating(text: String?) { - addRating(text.toRatingInt()) + this.score = Score.from10(text) } + @Deprecated("Use addScore", replaceWith = ReplaceWith("addScore")) fun LoadResponse.addRating(value: Int?) { - if ((value ?: return) < 0 || value > 10000) { - return - } - this.rating = value + this.score = Score.fromOld(value) } fun LoadResponse.addDuration(input: String?) { @@ -1741,7 +1960,7 @@ fun EpisodeResponse.addSeasonNames(names: List) { * @see newTorrentLoadResponse */ data class TorrentLoadResponse -@Deprecated("Use newTorrentLoadResponse method", level = DeprecationLevel.WARNING) +@Deprecated("Use newTorrentLoadResponse method", level = DeprecationLevel.ERROR) constructor( override var name: String, override var url: String, @@ -1752,7 +1971,7 @@ constructor( override var type: TvType = TvType.Torrent, override var posterUrl: String? = null, override var year: Int? = null, - override var rating: Int? = null, + override var score: Score? = null, override var tags: List? = null, override var duration: Int? = null, override var trailers: MutableList = mutableListOf(), @@ -1764,16 +1983,16 @@ constructor( override var backgroundPosterUrl: String? = null, override var contentRating: String? = null, @Prerelease - override var uniqueUrl : String = url + override var uniqueUrl: String = url ) : LoadResponse { /** * Secondary constructor for backwards compatibility without contentRating. * Remove this constructor after there is a new stable release and extensions are updated to support contentRating. */ - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") @Deprecated( "Use newTorrentLoadResponse method with contentRating included", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) constructor( name: String, @@ -1805,7 +2024,7 @@ constructor( type, posterUrl, year, - rating, + Score.fromOld(rating), tags, duration, trailers, @@ -1826,7 +2045,7 @@ suspend fun MainAPI.newTorrentLoadResponse( torrent: String? = null, initializer: suspend TorrentLoadResponse.() -> Unit = { } ): TorrentLoadResponse { - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") val builder = TorrentLoadResponse( name = name, url = url, @@ -1845,7 +2064,7 @@ suspend fun MainAPI.newTorrentLoadResponse( * @see newAnimeLoadResponse * */ data class AnimeLoadResponse -@Deprecated("Use newAnimeLoadResponse method", level = DeprecationLevel.WARNING) +@Deprecated("Use newAnimeLoadResponse method", level = DeprecationLevel.ERROR) constructor( var engName: String? = null, var japName: String? = null, @@ -1864,7 +2083,7 @@ constructor( override var tags: List? = null, var synonyms: List? = null, - override var rating: Int? = null, + override var score: Score? = null, override var duration: Int? = null, override var trailers: MutableList = mutableListOf(), override var recommendations: List? = null, @@ -1877,8 +2096,9 @@ constructor( override var backgroundPosterUrl: String? = null, override var contentRating: String? = null, @Prerelease - override var uniqueUrl : String = url + override var uniqueUrl: String = url ) : LoadResponse, EpisodeResponse { + override fun getLatestEpisodes(): Map { return episodes.map { (status, episodes) -> val maxSeason = episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE } @@ -1908,7 +2128,7 @@ constructor( * Secondary constructor for backwards compatibility without contentRating. * Remove this constructor after there is a new stable release and extensions are updated to support contentRating. */ - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") @Deprecated( "Use newAnimeLoadResponse method with contentRating included", level = DeprecationLevel.WARNING @@ -1952,7 +2172,7 @@ constructor( plot, tags, synonyms, - rating, + Score.fromOld(rating), duration, trailers, recommendations, @@ -2000,7 +2220,7 @@ suspend fun MainAPI.newAnimeLoadResponse( * @see newLiveStreamLoadResponse * */ data class LiveStreamLoadResponse -@Deprecated("Use newLiveStreamLoadResponse method", level = DeprecationLevel.WARNING) +@Deprecated("Use newLiveStreamLoadResponse method", level = DeprecationLevel.ERROR) constructor( override var name: String, override var url: String, @@ -2012,7 +2232,7 @@ constructor( override var plot: String? = null, override var type: TvType = TvType.Live, - override var rating: Int? = null, + override var score: Score? = null, override var tags: List? = null, override var duration: Int? = null, override var trailers: MutableList = mutableListOf(), @@ -2024,13 +2244,13 @@ constructor( override var backgroundPosterUrl: String? = null, override var contentRating: String? = null, @Prerelease - override var uniqueUrl : String = url + override var uniqueUrl: String = url ) : LoadResponse { /** * Secondary constructor for backwards compatibility without contentRating. * Remove this constructor after there is a new stable release and extensions are updated to support contentRating. */ - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") @Deprecated( "Use newLiveStreamLoadResponse method with contentRating included", level = DeprecationLevel.WARNING @@ -2055,8 +2275,25 @@ constructor( posterHeaders: Map? = null, backgroundPosterUrl: String? = null, ) : this( - name, url, apiName, dataUrl, posterUrl, year, plot, type, rating, tags, duration, trailers, - recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null + name, + url, + apiName, + dataUrl, + posterUrl, + year, + plot, + type, + Score.fromOld(rating), + tags, + duration, + trailers, + recommendations, + actors, + comingSoon, + syncData, + posterHeaders, + backgroundPosterUrl, + null ) } @@ -2066,7 +2303,7 @@ suspend fun MainAPI.newLiveStreamLoadResponse( dataUrl: String, initializer: suspend LiveStreamLoadResponse.() -> Unit = { } ): LiveStreamLoadResponse { - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") val builder = LiveStreamLoadResponse( name = name, url = url, @@ -2082,7 +2319,7 @@ suspend fun MainAPI.newLiveStreamLoadResponse( * @see newMovieLoadResponse * */ data class MovieLoadResponse -@Deprecated("Use newMovieLoadResponse method", level = DeprecationLevel.WARNING) +@Deprecated("Use newMovieLoadResponse method", level = DeprecationLevel.ERROR) constructor( override var name: String, override var url: String, @@ -2094,7 +2331,7 @@ constructor( override var year: Int? = null, override var plot: String? = null, - override var rating: Int? = null, + override var score: Score? = null, override var tags: List? = null, override var duration: Int? = null, override var trailers: MutableList = mutableListOf(), @@ -2106,16 +2343,16 @@ constructor( override var backgroundPosterUrl: String? = null, override var contentRating: String? = null, @Prerelease - override var uniqueUrl : String = url + override var uniqueUrl: String = url ) : LoadResponse { /** * Secondary constructor for backwards compatibility without contentRating. * Remove this constructor after there is a new stable release and extensions are updated to support contentRating. */ - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") @Deprecated( "Use newMovieLoadResponse method with contentRating included", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) constructor( name: String, @@ -2137,8 +2374,25 @@ constructor( posterHeaders: Map? = null, backgroundPosterUrl: String? = null, ) : this( - name, url, apiName, type, dataUrl, posterUrl, year, plot, rating, tags, duration, trailers, - recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null + name, + url, + apiName, + type, + dataUrl, + posterUrl, + year, + plot, + Score.fromOld(rating), + tags, + duration, + trailers, + recommendations, + actors, + comingSoon, + syncData, + posterHeaders, + backgroundPosterUrl, + null ) } @@ -2159,7 +2413,7 @@ suspend fun MainAPI.newMovieLoadResponse( ) val dataUrl = data?.toJson() ?: "" - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") val builder = MovieLoadResponse( name = name, url = url, @@ -2179,7 +2433,7 @@ suspend fun MainAPI.newMovieLoadResponse( dataUrl: String, initializer: suspend MovieLoadResponse.() -> Unit = { } ): MovieLoadResponse { - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") val builder = MovieLoadResponse( name = name, url = url, @@ -2198,30 +2452,40 @@ suspend fun MainAPI.newMovieLoadResponse( * @property season Season number. * @property episode Episode number. * @property posterUrl URL of Episode's poster image. - * @property rating Episode rating. + * @property score Episode rating. * @property date Episode air date, see addDate. * @property runTime Episode runtime in seconds. * @see newEpisode * */ data class Episode -@Deprecated("Use newEpisode", level = DeprecationLevel.WARNING) +@Deprecated("Use newEpisode", level = DeprecationLevel.ERROR) constructor( var data: String, var name: String? = null, var season: Int? = null, var episode: Int? = null, var posterUrl: String? = null, - var rating: Int? = null, + var score: Score? = null, var description: String? = null, var date: Long? = null, var runTime: Int? = null, ) { + @Deprecated( + "`rating` is the old scoring system, use score instead", + replaceWith = ReplaceWith("score") + ) + var rating: Int? + set(value) { + this.score = Score.from(value, 100) + } + get() = score?.toInt(100) + /** * Secondary constructor for backwards compatibility without runTime. * TODO Remove this constructor after there is a new stable release and extensions are updated to support runTime. */ - @Suppress("DEPRECATION") - @Deprecated("Use newEpisode with runTime included", level = DeprecationLevel.WARNING) + @Suppress("DEPRECATION_ERROR") + @Deprecated("Use newEpisode with runTime included", level = DeprecationLevel.ERROR) constructor( data: String, name: String? = null, @@ -2232,7 +2496,7 @@ constructor( description: String? = null, date: Long? = null, ) : this( - data, name, season, episode, posterUrl, rating, description, date, null + data, name, season, episode, posterUrl, Score.fromOld(rating), description, date, null ) } @@ -2253,7 +2517,7 @@ fun MainAPI.newEpisode( initializer: Episode.() -> Unit = { }, fix: Boolean = true, ): Episode { - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") val builder = Episode( data = if (fix) fixUrl(url) else url ) @@ -2270,7 +2534,7 @@ fun MainAPI.newEpisode( initializer = initializer ) // just in case java is wack - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") val builder = Episode( data = data?.toJson() ?: throw ErrorLoadingException("invalid newEpisode") ) @@ -2300,7 +2564,7 @@ enum class SimklSyncServices(val originalName: String) { * @see newTvSeriesLoadResponse * */ data class TvSeriesLoadResponse -@Deprecated("Use newTvSeriesLoadResponse method", level = DeprecationLevel.WARNING) +@Deprecated("Use newTvSeriesLoadResponse method", level = DeprecationLevel.ERROR) constructor( override var name: String, override var url: String, @@ -2313,7 +2577,7 @@ constructor( override var plot: String? = null, override var showStatus: ShowStatus? = null, - override var rating: Int? = null, + override var score: Score? = null, override var tags: List? = null, override var duration: Int? = null, override var trailers: MutableList = mutableListOf(), @@ -2327,7 +2591,7 @@ constructor( override var backgroundPosterUrl: String? = null, override var contentRating: String? = null, @Prerelease - override var uniqueUrl : String = url + override var uniqueUrl: String = url ) : LoadResponse, EpisodeResponse { override fun getLatestEpisodes(): Map { val maxSeason = @@ -2355,10 +2619,10 @@ constructor( * Secondary constructor for backwards compatibility without contentRating. * Remove this constructor after there is a new stable release and extensions are updated to support contentRating. */ - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") @Deprecated( "Use newTvSeriesLoadResponse method with contentRating included", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) constructor( name: String, @@ -2392,7 +2656,7 @@ constructor( year, plot, showStatus, - rating, + Score.fromOld(rating), tags, duration, trailers, @@ -2415,7 +2679,7 @@ suspend fun MainAPI.newTvSeriesLoadResponse( episodes: List, initializer: suspend TvSeriesLoadResponse.() -> Unit = { } ): TvSeriesLoadResponse { - @Suppress("DEPRECATION") + @Suppress("DEPRECATION_ERROR") val builder = TvSeriesLoadResponse( name = name, url = url, diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/MyDramaList.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/MyDramaList.kt index 1dd2ee79e38..a4ccab8c11e 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/MyDramaList.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/MyDramaList.kt @@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainPageRequest import com.lagradost.cloudstream3.ProviderType +import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.ShowStatus import com.lagradost.cloudstream3.TvType @@ -173,7 +174,7 @@ abstract class MyDramaListAPI : MainAPI() { this.posterUrl = media.images.poster this.year = media.mediaYear this.plot = media.synopsis - this.rating = media.mediaRating.times(1000).toInt() + this.score = Score.from10(media.mediaRating) this.tags = media.fixGenres() this.duration = media.runtime.toInt() this.recommendations = media.fetchRecommendations().map { it.toSearchResponse() } @@ -350,7 +351,7 @@ abstract class MyDramaListAPI : MainAPI() { season = null episode = ep.episodeNumber posterUrl = null - rating = ep.rating.times(1000).toInt() + score = Score.from10(ep.rating) description = null runTime = null addDate(ep.releasedAt) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt index 70732c5ef65..fa8a73b4968 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt @@ -180,7 +180,7 @@ open class TmdbProvider : MainAPI() { tags = genres?.mapNotNull { it.name } duration = episode_run_time?.average()?.toInt() - rating = this@toLoadResponse.rating + // score = Score.from10(this@toLoadResponse.rating) No docs on this? addTrailer(videos.toTrailers()) recommendations = (this@toLoadResponse.recommendations @@ -224,7 +224,7 @@ open class TmdbProvider : MainAPI() { addImdbId(external_ids?.imdb_id) tags = genres?.mapNotNull { it.name } duration = runtime - rating = this@toLoadResponse.rating + // score = Score.from10(this@toLoadResponse.rating) No docs on this? addTrailer(videos.toTrailers()) recommendations = (this@toLoadResponse.recommendations diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt index 1dd0df7df74..d040886fa61 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt @@ -9,12 +9,14 @@ import com.lagradost.cloudstream3.Episode import com.lagradost.cloudstream3.HomePageResponse import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.LoadResponse.Companion.addImdbId +import com.lagradost.cloudstream3.LoadResponse.Companion.addRating import com.lagradost.cloudstream3.LoadResponse.Companion.addTMDbId import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainPageRequest import com.lagradost.cloudstream3.NextAiring import com.lagradost.cloudstream3.ProviderType +import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.ShowStatus import com.lagradost.cloudstream3.TvType @@ -178,7 +180,7 @@ open class TraktProvider : MainAPI() { this.posterUrl = getOriginalWidthImageUrl(posterUrl) this.year = mediaDetails.year this.plot = mediaDetails.overview - this.rating = mediaDetails.rating?.times(1000)?.roundToInt() + this.score = Score.from10(mediaDetails.rating) this.tags = mediaDetails.genres this.duration = mediaDetails.runtime this.recommendations = relatedMedia @@ -266,7 +268,7 @@ open class TraktProvider : MainAPI() { this.year = mediaDetails.year this.plot = mediaDetails.overview this.showStatus = getStatus(mediaDetails.status) - this.rating = mediaDetails.rating?.times(1000)?.roundToInt() + this.score = Score.from10(mediaDetails.rating) this.tags = mediaDetails.genres this.duration = mediaDetails.runtime this.recommendations = relatedMedia From 276bf9f986406f68149dbeee752dee3329be6545 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 19 Jul 2025 19:04:22 +0000 Subject: [PATCH 002/618] Fix(UI): Updated style for buttons in dialog, Closes #1753 --- .../drawable/outline_drawable_less_inset.xml | 5 +++++ app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/styles.xml | 19 +++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 app/src/main/res/drawable/outline_drawable_less_inset.xml diff --git a/app/src/main/res/drawable/outline_drawable_less_inset.xml b/app/src/main/res/drawable/outline_drawable_less_inset.xml new file mode 100644 index 00000000000..29096d867bb --- /dev/null +++ b/app/src/main/res/drawable/outline_drawable_less_inset.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 631201b13f8..ca98f2403eb 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -22,4 +22,5 @@ 1dp 100dp + 5dp \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 6a849238316..24c69cafa79 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -455,6 +455,10 @@ @dimen/abc_dialog_min_width_minor @drawable/dialog__window_background false + + @style/WhiteButton.Dialog + @style/BlackButton.Dialog + @style/BlackButton.Dialog + + + + + + + + + ?attr/textColor + 10sp + + @drawable/kid_star_24px + + + + + + + + + From c783e82c80a1f8cf7577db4e29cc44b64e5b3edf Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:06:24 +0200 Subject: [PATCH 103/618] Fix: Minor typo + testing fix --- .../java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt | 2 +- app/src/main/res/layout/fragment_result_tv.xml | 2 +- app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index 3d5367fc607..4c5cdea5bee 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -143,7 +143,7 @@ class ExampleInstrumentedTest { Assert.assertTrue("Api does not contain a name", api.name != "NONE") Assert.assertTrue( "Api ${api.name} does not contain a valid language code", - langTagsIETF.contains(api.lang, ignoreCase = true) + langTagsIETF.contains(api.lang) ) Assert.assertTrue( "Api ${api.name} does not contain any supported types", diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 461a712d9e8..dbebecfcce2 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -285,7 +285,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_gravity="center_vertical" tools:text="69m\nremaining" /> - e + Date: Thu, 2 Oct 2025 19:16:13 +0200 Subject: [PATCH 104/618] Chore: Cleanup unused xml files --- app/src/main/res/layout/player_episodes.xml | 8 -- .../main/res/layout/player_episodes_large.xml | 105 ---------------- .../main/res/layout/player_episodes_small.xml | 56 --------- .../res/layout/result_episode_both_old.xml | 14 --- .../res/layout/result_episode_both_tv_old.xml | 21 ---- .../layout/result_episode_large_tv_old.xml | 112 ------------------ .../main/res/layout/result_episode_tv_old.xml | 59 --------- 7 files changed, 375 deletions(-) delete mode 100644 app/src/main/res/layout/player_episodes.xml delete mode 100644 app/src/main/res/layout/player_episodes_large.xml delete mode 100644 app/src/main/res/layout/player_episodes_small.xml delete mode 100644 app/src/main/res/layout/result_episode_both_old.xml delete mode 100644 app/src/main/res/layout/result_episode_both_tv_old.xml delete mode 100644 app/src/main/res/layout/result_episode_large_tv_old.xml delete mode 100644 app/src/main/res/layout/result_episode_tv_old.xml diff --git a/app/src/main/res/layout/player_episodes.xml b/app/src/main/res/layout/player_episodes.xml deleted file mode 100644 index a491bc0961f..00000000000 --- a/app/src/main/res/layout/player_episodes.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/player_episodes_large.xml b/app/src/main/res/layout/player_episodes_large.xml deleted file mode 100644 index 476965635c3..00000000000 --- a/app/src/main/res/layout/player_episodes_large.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/player_episodes_small.xml b/app/src/main/res/layout/player_episodes_small.xml deleted file mode 100644 index 62dd4bca1b0..00000000000 --- a/app/src/main/res/layout/player_episodes_small.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/result_episode_both_old.xml b/app/src/main/res/layout/result_episode_both_old.xml deleted file mode 100644 index 6472ecc1002..00000000000 --- a/app/src/main/res/layout/result_episode_both_old.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/result_episode_both_tv_old.xml b/app/src/main/res/layout/result_episode_both_tv_old.xml deleted file mode 100644 index f273a118078..00000000000 --- a/app/src/main/res/layout/result_episode_both_tv_old.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/result_episode_large_tv_old.xml b/app/src/main/res/layout/result_episode_large_tv_old.xml deleted file mode 100644 index 3a7cef3ca98..00000000000 --- a/app/src/main/res/layout/result_episode_large_tv_old.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/result_episode_tv_old.xml b/app/src/main/res/layout/result_episode_tv_old.xml deleted file mode 100644 index 62546cf9475..00000000000 --- a/app/src/main/res/layout/result_episode_tv_old.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file From 373d746a3f0baeba695c6496a300f90fa5e404a4 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Thu, 2 Oct 2025 23:04:48 +0530 Subject: [PATCH 105/618] feat(library):scroll top on sort (#1938) --- .../com/lagradost/cloudstream3/ui/library/LibraryFragment.kt | 1 + .../com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index bfac72067f0..7555c2acaf4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -416,6 +416,7 @@ class LibraryFragment : Fragment() { libraryViewModel.currentPage.value?.let { page -> binding?.viewpager?.setCurrentItem(page, false) + binding?.searchBar?.setExpanded(true) } updateRandom() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt index 0110187f603..392bf9cb3bc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -58,7 +58,6 @@ class ViewpagerAdapter( LibraryViewpagerPageBinding.inflate(LayoutInflater.from(parent.context), parent, false) ) } - override fun onUpdateContent( holder: ViewHolderState, item: SyncAPI.Page, @@ -67,6 +66,7 @@ class ViewpagerAdapter( val binding = holder.view if (binding !is LibraryViewpagerPageBinding) return (binding.pageRecyclerview.adapter as? PageAdapter)?.updateList(item.items) + binding.pageRecyclerview.scrollToPosition(0) } override fun onBindContent(holder: ViewHolderState, item: SyncAPI.Page, position: Int) { From 6d4c530e16d90ee946efa4ce6599efe4140e0760 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:52:08 +0200 Subject: [PATCH 106/618] Chore: Bump media3 to 1.8.0 --- .../cloudstream3/ui/player/UpdatedMatroskaExtractor.kt | 2 +- gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/UpdatedMatroskaExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/UpdatedMatroskaExtractor.kt index 06b4c12c335..016e7d20317 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/UpdatedMatroskaExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/UpdatedMatroskaExtractor.kt @@ -41,7 +41,7 @@ import androidx.media3.container.NalUnitUtil import androidx.media3.extractor.AacUtil import androidx.media3.extractor.AvcConfig import androidx.media3.extractor.ChunkIndex -import androidx.media3.extractor.DolbyVisionConfig +import androidx.media3.container.DolbyVisionConfig import androidx.media3.extractor.Extractor import androidx.media3.extractor.ExtractorInput import androidx.media3.extractor.ExtractorOutput diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7a3fd9165be..987ad3cd64b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ kotlinxCoroutinesCore = "1.10.1" lifecycleLivedataKtx = "2.8.7" lifecycleViewmodelKtx = "2.8.7" material = "1.12.0" -media3 = "1.6.1" +media3 = "1.8.0" navigationKtx = "2.8.9" newpipeextractor = "v0.24.6" nextlibMedia3 = "0.8.4" From 26570d688fce0cc4d8c51c32135dfa7575b1b476 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:54:54 +0200 Subject: [PATCH 107/618] Chore: Bump versionName --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 080f9dd2b3a..b40b19f2064 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -63,7 +63,7 @@ android { minSdk = libs.versions.minSdk.get().toInt() targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 66 - versionName = "4.5.5" + versionName = "4.5.6" resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "commit_hash", getGitCommitHash()) From ccf3b0508841782e7db7e2530b01ba1ffc72dd9b Mon Sep 17 00:00:00 2001 From: Phisher98 <153359846+phisher98@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:05:44 +0530 Subject: [PATCH 108/618] Dailymotion Fix (#1947) * Dailymotion Improvement * Dailymotion Improvement Minor Fix --- .../cloudstream3/extractors/Dailymotion.kt | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt index aa5e60c32eb..f15273c37a2 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.extractors +import com.google.gson.Gson import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi @@ -8,6 +9,7 @@ import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 import java.net.URI + class Geodailymotion : Dailymotion() { override val name = "GeoDailymotion" override val mainUrl = "https://geo.dailymotion.com" @@ -30,29 +32,26 @@ open class Dailymotion : ExtractorApi() { val embedUrl = getEmbedUrl(url) ?: return val id = getVideoId(embedUrl) ?: return val metaDataUrl = "$baseUrl/player/metadata/video/$id" - val response = app.get(metaDataUrl, referer = embedUrl).text - val qualityUrlRegex = Regex(""""url"\s*:\s*"([^"]+)"""") - val subtitlesRegex = Regex(""""subtitles"\s*:\s*\{[^}]*"data"\s*:\s*(\[[^\]]*\])""") - val urls = qualityUrlRegex.findAll(response) - .map { it.groupValues[1] } - .toList().filter { it.contains(".m3u8") } + val response = app.get(metaDataUrl, referer = embedUrl).text + val gson = Gson() + val meta = gson.fromJson(response, MetaData::class.java) - urls.forEach { videoUrl -> - getStream(videoUrl, this.name, callback) + meta.qualities?.get("auto")?.forEach { quality -> + val videoUrl = quality.url + if (!videoUrl.isNullOrEmpty() && videoUrl.contains(".m3u8")) { + getStream(videoUrl, this.name, callback) + } } - val subtitlesMatches = subtitlesRegex.findAll(response).map { it.groupValues[1] }.toList() - subtitlesMatches.forEach { subtitleJson -> - val subRegex = Regex("""\{\s*"label"\s*:\s*"([^"]+)",\s*"urls"\s*:\s*\["([^"]+)"""") - subRegex.findAll(subtitleJson).forEach { match -> - val label = match.groupValues[1] - val subUrl = match.groupValues[2] - subtitleCallback(SubtitleFile(label, subUrl)) + meta.subtitles?.data?.forEach { (_, subData) -> + subData.urls.forEach { subUrl -> + subtitleCallback(SubtitleFile(subData.label, subUrl)) } } } + private fun getEmbedUrl(url: String): String? { if (url.contains("/embed/") || url.contains("/video/")) return url if (url.contains("geo.dailymotion.com")) { @@ -76,4 +75,26 @@ open class Dailymotion : ExtractorApi() { ) { return generateM3u8(name, streamLink, "").forEach(callback) } + + + data class MetaData( + val qualities: Map>?, + val subtitles: SubtitlesWrapper? + ) + + data class Quality( + val type: String?, + val url: String? + ) + + data class SubtitlesWrapper( + val enable: Boolean, + val data: Map? + ) + + data class SubtitleData( + val label: String, + val urls: List + ) + } \ No newline at end of file From 6632f764b0cdb53a7e843057916cf524fa862cf7 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 3 Oct 2025 22:22:39 +0200 Subject: [PATCH 109/618] Feat: New TV UI for Homepage (#1942) --- .../lagradost/cloudstream3/MainActivity.kt | 41 +- .../ui/home/HomeParentItemAdapterPreview.kt | 66 ++-- app/src/main/res/layout/activity_main_tv.xml | 9 +- .../main/res/layout/fragment_home_head_tv.xml | 361 +++++++++--------- app/src/main/res/layout/fragment_home_tv.xml | 2 +- app/src/main/res/layout/rail_header.xml | 2 +- app/src/main/res/values/dimens.xml | 2 +- 7 files changed, 251 insertions(+), 232 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index c8d9d6a8b77..4a3886d50e5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -404,6 +404,24 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa } return false } + + + fun centerView(view: View?) { + if (view == null) return + try { + Log.v(TAG, "centerView: $view") + val r = Rect(0, 0, 0, 0) + view.getDrawingRect(r) + val x = r.centerX() + val y = r.centerY() + val dx = r.width() / 2 //screenWidth / 2 + val dy = screenHeight / 2 + val r2 = Rect(x - dx, y - dy, x + dx, y + dy) + view.requestRectangleOnScreen(r2, false) + // TvFocus.current =TvFocus.current.copy(y=y.toFloat()) + } catch (_: Throwable) { + } + } } @@ -484,7 +502,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa ).contains(destination.id) - val dontPush = listOf( + /*val dontPush = listOf( R.id.navigation_home, R.id.navigation_search, R.id.navigation_results_phone, @@ -515,7 +533,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa } layoutParams = params - } + }*/ val landscape = when (resources.configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> { @@ -1140,23 +1158,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa } } - private fun centerView(view: View?) { - if (view == null) return - try { - Log.v(TAG, "centerView: $view") - val r = Rect(0, 0, 0, 0) - view.getDrawingRect(r) - val x = r.centerX() - val y = r.centerY() - val dx = r.width() / 2 //screenWidth / 2 - val dy = screenHeight / 2 - val r2 = Rect(x - dx, y - dy, x + dx, y + dy) - view.requestRectangleOnScreen(r2, false) - // TvFocus.current =TvFocus.current.copy(y=y.toFloat()) - } catch (_: Throwable) { - } - } - @Suppress("DEPRECATION_ERROR") override fun onCreate(savedInstanceState: Bundle?) { app.initClient(this) @@ -1228,7 +1229,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa if (isLayout(TV)) { // Put here any button you don't want focusing it to center the view val exceptionButtons = listOf( - R.id.home_preview_play_btt, + //R.id.home_preview_play_btt, R.id.home_preview_info_btt, R.id.home_preview_hidden_next_focus, R.id.home_preview_hidden_prev_focus, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index dd2bf7bbcb9..58167e0c2b2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -108,7 +108,7 @@ class HomeParentItemAdapterPreview( ) } - return HeaderViewHolder(binding, viewModel,accountViewModel, fragment = fragment) + return HeaderViewHolder(binding, viewModel, accountViewModel, fragment = fragment) } override fun onBindHeader(holder: ViewHolderState) { @@ -116,7 +116,10 @@ class HomeParentItemAdapterPreview( } private class HeaderViewHolder( - val binding: ViewBinding, val viewModel: HomeViewModel,accountViewModel: AccountViewModel, fragment: Fragment, + val binding: ViewBinding, + val viewModel: HomeViewModel, + accountViewModel: AccountViewModel, + fragment: Fragment, ) : ViewHolderState(binding) { @@ -305,10 +308,13 @@ class HomeParentItemAdapterPreview( itemView.findViewById(R.id.home_bookmarked_child_recyclerview) private val headProfilePic: ImageView? = itemView.findViewById(R.id.home_head_profile_pic) - private val headProfilePicCard: View? = itemView.findViewById(R.id.home_head_profile_padding) + private val headProfilePicCard: View? = + itemView.findViewById(R.id.home_head_profile_padding) - private val alternateHeadProfilePic: ImageView? = itemView.findViewById(R.id.alternate_home_head_profile_pic) - private val alternateHeadProfilePicCard: View? = itemView.findViewById(R.id.alternate_home_head_profile_padding) + private val alternateHeadProfilePic: ImageView? = + itemView.findViewById(R.id.alternate_home_head_profile_pic) + private val alternateHeadProfilePicCard: View? = + itemView.findViewById(R.id.alternate_home_head_profile_padding) private val topPadding: View? = itemView.findViewById(R.id.home_padding) @@ -334,7 +340,7 @@ class HomeParentItemAdapterPreview( homePreviewTags.isGone = item.tags.isNullOrEmpty() - homePreviewPlayBtt.setOnClickListener { view -> + /*homePreviewPlayBtt.setOnClickListener { view -> viewModel.click( LoadClickCallback( START_ACTION_RESUME_LATEST, @@ -343,7 +349,7 @@ class HomeParentItemAdapterPreview( item ) ) - } + }*/ homePreviewInfoBtt.setOnClickListener { view -> viewModel.click( @@ -494,7 +500,7 @@ class HomeParentItemAdapterPreview( activity?.showAccountSelectLinear() } - fun showAccountEditBox(context:Context): Boolean { + fun showAccountEditBox(context: Context): Boolean { val currentAccount = DataStoreHelper.getCurrentAccount() return if (currentAccount != null) { showAccountEditDialog( @@ -502,16 +508,21 @@ class HomeParentItemAdapterPreview( account = currentAccount, isNewAccount = false, accountEditCallback = { accountViewModel.handleAccountUpdate(it, context) }, - accountDeleteCallback = { accountViewModel.handleAccountDelete(it, context) } + accountDeleteCallback = { + accountViewModel.handleAccountDelete( + it, + context + ) + } ) true - }else false + } else false } - alternateHeadProfilePicCard?.setOnLongClickListener{ + alternateHeadProfilePicCard?.setOnLongClickListener { showAccountEditBox(it.context) } - headProfilePicCard?.setOnLongClickListener{ + headProfilePicCard?.setOnLongClickListener { showAccountEditBox(it.context) } @@ -525,8 +536,12 @@ class HomeParentItemAdapterPreview( viewModel.loadAndCancel(api, forceReload = true, fromUI = true) } } - homePreviewReloadProvider.setOnClickListener{ - viewModel.loadAndCancel(viewModel.apiName.value ?: noneApi.name, forceReload = true, fromUI = true) + homePreviewReloadProvider.setOnClickListener { + viewModel.loadAndCancel( + viewModel.apiName.value ?: noneApi.name, + forceReload = true, + fromUI = true + ) showToast(R.string.action_reload, Toast.LENGTH_SHORT) true } @@ -535,14 +550,18 @@ class HomeParentItemAdapterPreview( viewModel.queryTextSubmit("") } - // This makes the hidden next buttons only available when on the info button - // Otherwise you might be able to go to the next item without being at the info button - homePreviewInfoBtt.setOnFocusChangeListener { _, hasFocus -> - homePreviewHiddenNextFocus.isFocusable = hasFocus - } - - homePreviewPlayBtt.setOnFocusChangeListener { _, hasFocus -> - homePreviewHiddenPrevFocus.isFocusable = hasFocus + // A workaround to the focus problem of always centering the view on focus + // as that causes higher android versions to stretch the ui when switching between shows + var lastFocusTimeoutMs = 0L + homePreviewInfoBtt.setOnFocusChangeListener { view, hasFocus -> + val lastFocusMs = lastFocusTimeoutMs + // Always reset timer, as we only want to update + // it if we have not interacted in half a second + lastFocusTimeoutMs = System.currentTimeMillis() + if (!hasFocus) return@setOnFocusChangeListener + if (lastFocusMs + 500L < System.currentTimeMillis()) { + MainActivity.centerView(view) + } } homePreviewHiddenNextFocus.setOnFocusChangeListener { _, hasFocus -> @@ -560,7 +579,8 @@ class HomeParentItemAdapterPreview( )?.requestFocus() } else { previewViewpager.setCurrentItem(previewViewpager.currentItem - 1, true) - binding.homePreviewPlayBtt.requestFocus() + binding.homePreviewInfoBtt.requestFocus() + //binding.homePreviewPlayBtt.requestFocus() } } } diff --git a/app/src/main/res/layout/activity_main_tv.xml b/app/src/main/res/layout/activity_main_tv.xml index 0003b26187d..1c4ebd8ef83 100644 --- a/app/src/main/res/layout/activity_main_tv.xml +++ b/app/src/main/res/layout/activity_main_tv.xml @@ -15,12 +15,13 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + android:nextFocusDown="@id/home_preview_info_btt"> + @@ -64,8 +144,8 @@ android:padding="10dp" android:src="@drawable/ic_refresh" android:tag="@string/tv_no_focus_tag" - app:tint="@color/player_on_button_tv_attr" - android:visibility="gone"/> + android:visibility="gone" + app:tint="@color/player_on_button_tv_attr" /> @@ -130,89 +210,6 @@ android:src="@drawable/ic_outline_account_circle_24" /> --> - - - - - - - - - - - - - - - - - - - - @@ -252,9 +249,9 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/navbar_width" android:layout_marginEnd="0dp" + android:background="?android:attr/selectableItemBackground" android:padding="12dp" android:text="@string/continue_watching" - android:background="?android:attr/selectableItemBackground" app:drawableTint="?attr/white" /> - - - - - - - - - - - + android:paddingBottom="5dp" + android:requiresFadingEdge="horizontal"> - - - + android:orientation="horizontal"> + + + + + + + + + + + + - + + android:nextFocusDown="@id/home_preview_info_btt" > diff --git a/app/src/main/res/layout/rail_header.xml b/app/src/main/res/layout/rail_header.xml index 8868b57befe..16ccd6c399a 100644 --- a/app/src/main/res/layout/rail_header.xml +++ b/app/src/main/res/layout/rail_header.xml @@ -13,7 +13,7 @@ android:layout_height="24dp" app:itemIconTint="@color/item_select_color" android:focusable="true" - android:nextFocusRight="@id/home_preview_play_btt" + android:nextFocusRight="@id/home_preview_info_btt" android:src="@drawable/notifications_icon_selector" android:contentDescription="@string/account" app:tint="@color/item_select_color" /> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index cf632e0d3b0..5bf1e87ed8a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -16,7 +16,7 @@ 2000 3dp - 62dp + 0dp 50dp 1dp From a70fb87594c71eab2396db4b77d9e8294bf993c2 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 4 Oct 2025 02:02:42 +0200 Subject: [PATCH 110/618] Feat: Infinite scrolling on quicksearch and trakt (#1949) --- .../ui/quicksearch/QuickSearchFragment.kt | 37 ++++++++-- .../cloudstream3/ui/search/SearchAdaptor.kt | 9 +-- .../cloudstream3/ui/search/SearchFragment.kt | 5 +- .../cloudstream3/ui/search/SearchViewModel.kt | 70 ++++++++++++------- .../metaproviders/TraktProvider.kt | 12 ++-- 5 files changed, 87 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt index d2e308a3c32..f428321d23a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt @@ -16,6 +16,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.CommonActivity.activity @@ -39,6 +40,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.AppContextUtils.filterProviderByPreferredMedia import com.lagradost.cloudstream3.utils.AppContextUtils.filterSearchResultByFilmQuality +import com.lagradost.cloudstream3.utils.AppContextUtils.isRecyclerScrollable import com.lagradost.cloudstream3.utils.AppContextUtils.ownShow import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.UIHelper @@ -137,7 +139,6 @@ class QuickSearchFragment : Fragment() { HomeFragment.currentSpan = it } binding?.quickSearchAutofitResults?.spanCount = HomeFragment.currentSpan - HomeFragment.currentSpan = HomeFragment.currentSpan HomeFragment.configEvent.invoke(HomeFragment.currentSpan) } @@ -160,7 +161,8 @@ class QuickSearchFragment : Fragment() { getApiFromNameNull(providers?.first())?.hasQuickSearch ?: false } else false - if (isSingleProvider) { + val firstProvider = providers?.firstOrNull() + if (isSingleProvider && firstProvider != null) { binding?.quickSearchAutofitResults?.apply { adapter = SearchAdapter( ArrayList(), @@ -170,9 +172,31 @@ class QuickSearchFragment : Fragment() { } } + binding?.quickSearchAutofitResults?.addOnScrollListener(object : + RecyclerView.OnScrollListener() { + var expandCount = 0 + + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + + val adapter = recyclerView.adapter + if (adapter !is SearchAdapter) return + + val count = adapter.itemCount + val currentHasNext = adapter.hasNext + + if (!recyclerView.isRecyclerScrollable() && currentHasNext && expandCount != count) { + expandCount = count + ioSafe { + searchViewModel.expandAndReturn(firstProvider) + } + } + } + }) + try { binding?.quickSearch?.queryHint = - getString(R.string.search_hint_site).format(providers?.first()) + getString(R.string.search_hint_site).format(firstProvider) } catch (e: Exception) { logError(e) } @@ -273,9 +297,12 @@ class QuickSearchFragment : Fragment() { when (it) { is Resource.Success -> { it.value.let { data -> - (binding?.quickSearchAutofitResults?.adapter as? SearchAdapter)?.updateList( - context?.filterSearchResultByFilmQuality(data) ?: data + val adapter = + (binding?.quickSearchAutofitResults?.adapter as? SearchAdapter) + adapter?.updateList( + context?.filterSearchResultByFilmQuality(data.list) ?: data.list ) + adapter?.hasNext = data.hasNext } searchExitIcon?.alpha = 1f binding?.quickSearchLoadingBar?.alpha = 0f diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index f318401c092..ed3fabe7169 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -31,7 +31,7 @@ class SearchClickCallback( ) class SearchAdapter( - private val cardList: MutableList, + private var cardList: List, private val resView: AutofitRecyclerView, private val clickCallback: (SearchClickCallback) -> Unit, ) : RecyclerView.Adapter() { @@ -74,12 +74,9 @@ class SearchAdapter( fun updateList(newList: List) { val diffResult = DiffUtil.calculateDiff( - SearchResponseDiffCallback(this.cardList, newList) + SearchResponseDiffCallback(cardList, newList) ) - - cardList.clear() - cardList.addAll(newList) - + cardList = newList diffResult.dispatchUpdatesTo(this) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 2c5f800901b..e8fcbc8510f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -492,8 +492,9 @@ class SearchFragment : Fragment() { when (it) { is Resource.Success -> { it.value.let { data -> - if (data.isNotEmpty()) { - (binding?.searchAutofitResults?.adapter as? SearchAdapter)?.updateList(data) + val list = data.list + if (list.isNotEmpty()) { + (binding?.searchAutofitResults?.adapter as? SearchAdapter)?.updateList(list) } } searchExitIcon?.alpha = 1f diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index a0d53354595..72a2bcb606b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -32,9 +32,9 @@ data class ExpandableSearchList( const val SEARCH_HISTORY_KEY = "search_history" class SearchViewModel : ViewModel() { - private val _searchResponse: MutableLiveData>> = + private val _searchResponse: MutableLiveData> = MutableLiveData() - val searchResponse: LiveData>> get() = _searchResponse + val searchResponse: LiveData> get() = _searchResponse private val _currentSearch: MutableLiveData> = MutableLiveData() @@ -46,7 +46,7 @@ class SearchViewModel : ViewModel() { private var repos = synchronized(apis) { apis.map { APIRepository(it) } } fun clearSearch() { - _searchResponse.postValue(Resource.Success(ArrayList())) + _searchResponse.postValue(Resource.Success(ExpandableSearchList(emptyList(), 0, false))) _currentSearch.postValue(emptyMap()) expandableSearches.clear() } @@ -105,15 +105,16 @@ class SearchViewModel : ViewModel() { if (next is Resource.Success) { val nextValue = next.value expandableSearches[name]?.apply { - hasNext = nextValue.hasNext - currentPage = nextPage + this.hasNext = nextValue.hasNext + this.currentPage = nextPage debugWarning({ nextValue.items.any { outer -> this.list.any { it.url == outer.url } } }) { "Expanded search contained an item that was previously already in the list.\nQuery = $query, ${nextValue.items} = ${this.list}" } - this.list += nextValue.items - this.list.distinctBy { it.url } // just to be sure we are not adding the same shit for some reason + // just to be sure we are not adding the same shit for some reason + // Avoids weird behavior in the recyclerview by recreating the list + this.list = (this.list + nextValue.items).distinctBy { it.url } } ?: debugWarning { "Expanded an item not in search load named $name, current list is ${expandableSearches.keys}" } @@ -121,14 +122,44 @@ class SearchViewModel : ViewModel() { current.hasNext = false } + _searchResponse.postValue(Resource.Success(bundleSearch(expandableSearches))) _currentSearch.postValue(expandableSearches) } - lock -= name val item = expandableSearches[name] ?: return null - return HomeViewModel.ExpandableHomepageList(HomePageList(name, item.list), item.currentPage, item.hasNext) + return HomeViewModel.ExpandableHomepageList( + HomePageList(name, item.list), + item.currentPage, + item.hasNext + ) + } + + private fun bundleSearch(lists: MutableMap): ExpandableSearchList { + if (lists.size == 1) { + return lists.values.first() + } + + val list = ArrayList() + val nestedList = + lists.map { it.value.list } + + // I do it this way to move the relevant search results to the top + var index = 0 + while (true) { + var added = 0 + for (sublist in nestedList) { + if (sublist.size > index) { + list.add(sublist[index]) + added++ + } + } + if (added == 0) break + index++ + } + + return ExpandableSearchList(list, 1, false) } private fun search( @@ -172,7 +203,8 @@ class SearchViewModel : ViewModel() { if (currentSearchIndex != currentIndex) return@amap if (search is Resource.Success) { val searchValue = search.value - expandableSearches[a.name] = ExpandableSearchList(searchValue.items, 1, searchValue.hasNext) + expandableSearches[a.name] = + ExpandableSearchList(searchValue.items, 1, searchValue.hasNext) } _currentSearch.postValue(expandableSearches) @@ -181,23 +213,7 @@ class SearchViewModel : ViewModel() { if (currentSearchIndex != currentIndex) return@withContext // this should prevent rewrite of existing data bug _currentSearch.postValue(expandableSearches) - val list = ArrayList() - val nestedList = - expandableSearches.map { it.value.list } - - // I do it this way to move the relevant search results to the top - var index = 0 - while (true) { - var added = 0 - for (sublist in nestedList) { - if (sublist.size > index) { - list.add(sublist[index]) - added++ - } - } - if (added == 0) break - index++ - } + val list = bundleSearch(expandableSearches) _searchResponse.postValue(Resource.Success(list)) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt index 8ac8f42bc7a..63f6d564c4b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt @@ -17,6 +17,7 @@ import com.lagradost.cloudstream3.NextAiring import com.lagradost.cloudstream3.ProviderType import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.SearchResponseList import com.lagradost.cloudstream3.ShowStatus import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.addDate @@ -27,6 +28,7 @@ import com.lagradost.cloudstream3.newEpisode import com.lagradost.cloudstream3.newHomePageResponse import com.lagradost.cloudstream3.newMovieLoadResponse import com.lagradost.cloudstream3.newMovieSearchResponse +import com.lagradost.cloudstream3.newSearchResponseList import com.lagradost.cloudstream3.newTvSeriesLoadResponse import com.lagradost.cloudstream3.newTvSeriesSearchResponse import com.lagradost.cloudstream3.utils.AppUtils.parseJson @@ -56,7 +58,6 @@ open class TraktProvider : MainAPI() { ) override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { - val apiResponse = getApi("${request.data}?extended=full,images&page=$page") val results = parseJson>(apiResponse).map { element -> @@ -97,17 +98,16 @@ open class TraktProvider : MainAPI() { } } - override suspend fun search(query: String): List? { + override suspend fun search(query: String, page: Int): SearchResponseList? { val apiResponse = - getApi("$traktApiUrl/search/movie,show?extended=full,images&limit=20&page=1&query=$query") + getApi("$traktApiUrl/search/movie,show?extended=full,images&limit=20&page=$page&query=$query") - return parseJson>(apiResponse).map { element -> + return newSearchResponseList(parseJson>(apiResponse).map { element -> element.toSearchResponse() - } + }) } override suspend fun load(url: String): LoadResponse { - val data = parseJson(url) val mediaDetails = data.mediaDetails val moviesOrShows = if (data.type == TvType.Movie) "movies" else "shows" From 60fab250327d95d49d45f152e40f7102e70e4667 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 4 Oct 2025 02:17:19 +0200 Subject: [PATCH 111/618] Fix: Minor margin issue (Phone) + Minor 1px rendering issue (TV) --- app/src/main/res/layout/fragment_result.xml | 4 +++- app/src/main/res/layout/fragment_result_tv.xml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 87de47336fc..79f89af25ea 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -644,6 +644,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" + android:layout_marginBottom="10dp" android:orientation="vertical" android:visibility="gone" tools:visibility="visible"> @@ -686,7 +687,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:paddingVertical="15dp" + android:paddingTop="5dp" + android:paddingBottom="15dp" android:visibility="gone" tools:visibility="visible"> diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index dbebecfcce2..57624bbf1c5 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -36,6 +36,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit tools:src="@drawable/profile_bg_dark_blue" /> Date: Sun, 5 Oct 2025 01:04:31 +0300 Subject: [PATCH 112/618] feat(TV UI): Crop the Main banner from the Top (#1952) --- .../com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt | 3 +++ app/src/main/res/layout/fragment_home_head_tv.xml | 3 +++ app/src/main/res/layout/home_scroll_view_tv.xml | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index 4c4dd2d84dc..e3b96ade29a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -57,6 +57,9 @@ class HomeScrollAdapter( } is HomeScrollViewTvBinding -> { + //Change poster crop area to 20% from Top + binding.homeScrollPreview.cropYCenterOffsetPct = 0.2f + binding.homeScrollPreview.loadImage(posterUrl) } } diff --git a/app/src/main/res/layout/fragment_home_head_tv.xml b/app/src/main/res/layout/fragment_home_head_tv.xml index f9d44da2404..88320eb9690 100644 --- a/app/src/main/res/layout/fragment_home_head_tv.xml +++ b/app/src/main/res/layout/fragment_home_head_tv.xml @@ -125,6 +125,7 @@ android:gravity="center_vertical" android:nextFocusLeft="@id/home_preview_info_btt" android:nextFocusRight="@id/home_preview_reload_provider" + android:nextFocusUp="@id/home_preview_change_api" android:nextFocusDown="@id/home_preview_info_btt"> @@ -140,6 +141,7 @@ android:focusable="true" android:nextFocusLeft="@id/home_preview_change_api" android:nextFocusRight="@id/home_preview_search_button" + android:nextFocusUp="@id/home_preview_reload_provider" android:nextFocusDown="@id/home_preview_info_btt" android:padding="10dp" android:src="@drawable/ic_refresh" @@ -157,6 +159,7 @@ android:focusable="true" android:nextFocusLeft="@id/home_preview_reload_provider" android:nextFocusRight="@id/home_preview_switch_account" + android:nextFocusUp="@id/home_preview_search_button" android:nextFocusDown="@id/home_preview_info_btt" android:padding="10dp" android:src="@drawable/search_icon" diff --git a/app/src/main/res/layout/home_scroll_view_tv.xml b/app/src/main/res/layout/home_scroll_view_tv.xml index e0da81975ca..d819c91c7e1 100644 --- a/app/src/main/res/layout/home_scroll_view_tv.xml +++ b/app/src/main/res/layout/home_scroll_view_tv.xml @@ -5,11 +5,11 @@ android:background="?attr/primaryGrayBackground" android:layout_height="match_parent"> - Date: Sun, 5 Oct 2025 00:26:27 +0200 Subject: [PATCH 113/618] Fix: HTML encoded description, and clickable homepage hero --- .../ui/home/HomeParentItemAdapterPreview.kt | 27 +++++++------------ .../cloudstream3/ui/home/HomeScrollAdapter.kt | 9 +++++-- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index 58167e0c2b2..c2f06c0210b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -14,9 +14,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.findViewTreeLifecycleOwner -import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import androidx.viewpager2.widget.ViewPager2 @@ -55,9 +53,9 @@ import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout +import com.lagradost.cloudstream3.utils.AppContextUtils.html import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.DataStoreHelper.getDefaultAccount import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes @@ -145,7 +143,12 @@ class HomeParentItemAdapterPreview( } } - val previewAdapter = HomeScrollAdapter(fragment = fragment) + val previewAdapter = HomeScrollAdapter(fragment = fragment) { view, position, item -> + viewModel.click( + LoadClickCallback(0, view, position, item) + ) + } + private val resumeAdapter = ResumeItemAdapter( fragment, nextFocusUp = itemView.nextFocusUpId, @@ -328,9 +331,9 @@ class HomeParentItemAdapterPreview( homePreviewDescription.isGone = item.plot.isNullOrBlank() homePreviewDescription.text = - item.plot ?: "" + item.plot?.html() ?: "" - homePreviewText.text = item.name + homePreviewText.text = item.name.html() populateChips( homePreviewTags, item.tags?.take(6) ?: emptyList(), @@ -340,23 +343,11 @@ class HomeParentItemAdapterPreview( homePreviewTags.isGone = item.tags.isNullOrEmpty() - /*homePreviewPlayBtt.setOnClickListener { view -> - viewModel.click( - LoadClickCallback( - START_ACTION_RESUME_LATEST, - view, - position, - item - ) - ) - }*/ - homePreviewInfoBtt.setOnClickListener { view -> viewModel.click( LoadClickCallback(0, view, position, item) ) } - } (binding as? FragmentHomeHeadBinding)?.apply { //homePreviewImage.setImage(item.posterUrl, item.posterHeaders) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index e3b96ade29a..3a6ed4923f2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.home import android.content.res.Configuration import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.core.view.isGone import androidx.fragment.app.Fragment @@ -16,7 +17,8 @@ import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.ImageLoader.loadImage class HomeScrollAdapter( - fragment: Fragment + fragment: Fragment, + val callback : ((View, Int, LoadResponse) -> Unit) ) : NoStateAdapter(fragment) { var hasMoreItems: Boolean = false @@ -59,7 +61,10 @@ class HomeScrollAdapter( is HomeScrollViewTvBinding -> { //Change poster crop area to 20% from Top binding.homeScrollPreview.cropYCenterOffsetPct = 0.2f - + binding.homeScrollPreview.isFocusable = false + binding.homeScrollPreview.setOnClickListener { view -> + callback.invoke(view ?: return@setOnClickListener, position, item) + } binding.homeScrollPreview.loadImage(posterUrl) } } From 233fe87cdc9c40b6155e9dded26937180f03d23f Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 5 Oct 2025 01:05:55 +0200 Subject: [PATCH 114/618] Fix(TV): Focus issue with account --- app/src/main/res/layout/rail_footer.xml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/layout/rail_footer.xml b/app/src/main/res/layout/rail_footer.xml index 137d126929c..de0406a6e26 100644 --- a/app/src/main/res/layout/rail_footer.xml +++ b/app/src/main/res/layout/rail_footer.xml @@ -1,32 +1,33 @@ + tools:visibility="visible"> + android:foreground="@drawable/outline_drawable_round_20" + android:nextFocusDown="@id/nav_footer_profile_card" + app:cardCornerRadius="20dp"> + tools:src="@drawable/profile_bg_orange" /> From ca737b11948aeeb872d8cc0382b76a15fdefe8b7 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 5 Oct 2025 02:20:45 +0200 Subject: [PATCH 115/618] Fix(TV): Poster size for remove item --- .../ui/home/HomeChildItemAdapter.kt | 132 +++++++++++------- .../cloudstream3/ui/home/HomeFragment.kt | 2 + .../ui/home/HomeParentItemAdapter.kt | 22 ++- .../ui/result/LinearListLayout.kt | 23 ++- 4 files changed, 115 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt index ae22afdb223..7ffb7ed0610 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt @@ -1,8 +1,10 @@ package com.lagradost.cloudstream3.ui.home +import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager import androidx.viewbinding.ViewBinding @@ -13,6 +15,7 @@ import com.lagradost.cloudstream3.databinding.HomeRemoveGridExpandedBinding import com.lagradost.cloudstream3.databinding.HomeResultGridBinding import com.lagradost.cloudstream3.databinding.HomeResultGridExpandedBinding import com.lagradost.cloudstream3.ui.BaseAdapter +import com.lagradost.cloudstream3.ui.BaseDiffCallback import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SearchClickCallback @@ -69,18 +72,25 @@ class ResumeItemAdapter( override fun onBindFooter(holder: ViewHolderState) { this.applyBinding(holder, false) + when (val binding = holder.view) { + is HomeRemoveGridBinding -> { + updateLayoutParms(binding.backgroundCard, setWidth, setHeight) + } + + is HomeRemoveGridExpandedBinding -> { + updateLayoutParms(binding.backgroundCard, setWidth, setHeight) + } + } holder.itemView.apply { if (isLayout(TV)) { isFocusableInTouchMode = true isFocusable = true } - - if (nextFocusUp != null) { - nextFocusUpId = nextFocusUp + nextFocusUp?.let { + nextFocusUpId = it } - - if (nextFocusDown != null) { - nextFocusDownId = nextFocusDown + nextFocusDown?.let { + nextFocusDownId = it } setOnClickListener { v -> @@ -90,16 +100,50 @@ class ResumeItemAdapter( } } +/** Remember to set `updatePosterSize` to cache the poster size, + * otherwise the width and height is unset */ open class HomeChildItemAdapter( fragment: Fragment, id: Int, - protected val nextFocusUp: Int? = null, - protected val nextFocusDown: Int? = null, - private val clickCallback: (SearchClickCallback) -> Unit, + var nextFocusUp: Int? = null, + var nextFocusDown: Int? = null, + var clickCallback: (SearchClickCallback) -> Unit, ) : - BaseAdapter(fragment, id) { - var isHorizontal: Boolean = false + BaseAdapter( + fragment, id, diffCallback = BaseDiffCallback( + itemSame = { a, b -> + a.url == b.url + }, + contentSame = { a, b -> + a == b + }) + ) { var hasNext: Boolean = false + var isHorizontal: Boolean = false + set(value) { + field = value + updateCachedPosterSize() + } + + private fun updateCachedPosterSize() { + setWidth = if (!isHorizontal) { + minPosterSize + } else { + maxPosterSize + } + setHeight = if (!isHorizontal) { + maxPosterSize + } else { + minPosterSize + } + } + + init { + updateCachedPosterSize() + } + + protected var setWidth = 0 + protected var setHeight = 0 override fun onCreateContent(parent: ViewGroup): ViewHolderState { val expanded = parent.context.isBottomLayout() @@ -112,52 +156,38 @@ open class HomeChildItemAdapter( return HomeScrollViewHolderState(binding) } - protected fun applyBinding(holder: ViewHolderState, isFirstItem: Boolean) { - val context = holder.view.root.context - val scale = PreferenceManager.getDefaultSharedPreferences(context) - ?.getInt(context.getString(R.string.poster_size_key), 0) ?: 0 - // Scale by +10% per step - val mul = 1.0f + scale * 0.1f - val min = (114.toPx.toFloat() * mul).toInt() - val max = (180.toPx.toFloat() * mul).toInt() + companion object { + var minPosterSize: Int = 0 + var maxPosterSize: Int = 0 + + fun updatePosterSize(context: Context) { + val scale = PreferenceManager.getDefaultSharedPreferences(context) + ?.getInt(context.getString(R.string.poster_size_key), 0) ?: 0 + // Scale by +10% per step + val mul = 1.0f + scale * 0.1f + minPosterSize = (114.toPx.toFloat() * mul).toInt() + maxPosterSize = (180.toPx.toFloat() * mul).toInt() + } + + fun updateLayoutParms(layout: FrameLayout, width: Int, height: Int) { + val params = layout.layoutParams + if (params.height == height && params.width == width) return + params.width = width + params.height = height + + layout.layoutParams = params + } + } + + protected fun applyBinding(holder: ViewHolderState, isFirstItem: Boolean) { when (val binding = holder.view) { is HomeResultGridBinding -> { - binding.backgroundCard.apply { - - layoutParams = - layoutParams.apply { - width = if (!isHorizontal) { - min - } else { - max - } - height = if (!isHorizontal) { - max - } else { - min - } - } - } + updateLayoutParms(binding.backgroundCard, setWidth, setHeight) } is HomeResultGridExpandedBinding -> { - binding.backgroundCard.apply { - - layoutParams = - layoutParams.apply { - width = if (!isHorizontal) { - min - } else { - max - } - height = if (!isHorizontal) { - max - } else { - min - } - } - } + updateLayoutParms(binding.backgroundCard, setWidth, setHeight) if (isFirstItem) { // to fix tv binding.backgroundCard.nextFocusLeftId = R.id.nav_rail_view diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 77bb163ee2d..5d6cbfb358d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -604,6 +604,8 @@ class HomeFragment : Fragment() { super.onViewCreated(view, savedInstanceState) fixGrid() + context?.let { HomeChildItemAdapter.updatePosterSize(it) } + binding?.apply { //homeChangeApiLoading.setOnClickListener(apiChangeClickListener) //homeChangeApiLoading.setOnClickListener(apiChangeClickListener) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 8bc0aa287ea..7cdd5670566 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -60,7 +60,7 @@ open class ParentItemAdapter( override fun restore(state: Bundle) { (binding as? HomepageParentBinding)?.homeChildRecyclerview?.layoutManager?.onRestoreInstanceState( - state.getSafeParcelable("value") + state.getSafeParcelable("value") ) } } @@ -101,6 +101,26 @@ open class ParentItemAdapter( hasNext = item.hasNext submitList(item.list.list) } + + /** We can reuse the HomeChildItemAdapter, but that causes weird a fade-in + * That is not really desirable. + * */ + /* + val currentAdapter = homeChildRecyclerview.adapter as? HomeChildItemAdapter + if (currentAdapter == null) { + ... + } else { + currentAdapter.apply { + isHorizontal = info.isHorizontalImages + hasNext = item.hasNext + this.clickCallback = this@ParentItemAdapter.clickCallback + nextFocusUp = homeChildRecyclerview.nextFocusUpId + nextFocusDown = homeChildRecyclerview.nextFocusDownId + submitList(item.list.list) + } + } + */ + homeChildRecyclerview.setLinearListLayout( isHorizontal = true, nextLeft = startFocus, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt index 7d0061cb687..408d213cfc1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt @@ -23,18 +23,17 @@ fun RecyclerView?.setLinearListLayout( ) { if (this == null) return val ctx = this.context ?: return - this.layoutManager = - LinearListLayout(ctx).apply { - if (isHorizontal) setHorizontal() else setVertical() - nextFocusLeft = - if (nextLeft == FOCUS_INHERIT) this@setLinearListLayout.nextFocusLeftId else nextLeft - nextFocusRight = - if (nextRight == FOCUS_INHERIT) this@setLinearListLayout.nextFocusRightId else nextRight - nextFocusUp = - if (nextUp == FOCUS_INHERIT) this@setLinearListLayout.nextFocusUpId else nextUp - nextFocusDown = - if (nextDown == FOCUS_INHERIT) this@setLinearListLayout.nextFocusDownId else nextDown - } + this.layoutManager = (this.layoutManager as? LinearListLayout ?: LinearListLayout(ctx)).apply { + if (isHorizontal) setHorizontal() else setVertical() + nextFocusLeft = + if (nextLeft == FOCUS_INHERIT) this@setLinearListLayout.nextFocusLeftId else nextLeft + nextFocusRight = + if (nextRight == FOCUS_INHERIT) this@setLinearListLayout.nextFocusRightId else nextRight + nextFocusUp = + if (nextUp == FOCUS_INHERIT) this@setLinearListLayout.nextFocusUpId else nextUp + nextFocusDown = + if (nextDown == FOCUS_INHERIT) this@setLinearListLayout.nextFocusDownId else nextDown + } } open class LinearListLayout(context: Context?) : From dcdf15f484904291cb70832dc0b185bf6a6dd574 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:18:30 +0200 Subject: [PATCH 116/618] Fix: Optimized homepage by properly recycling all nested recycleviews --- .../lagradost/cloudstream3/ui/BaseAdapter.kt | 16 +++++++++ .../ui/home/HomeParentItemAdapter.kt | 33 ++++++++----------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt index e930961c550..d095831965e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt @@ -85,6 +85,22 @@ abstract class BaseAdapter< AsyncDifferConfig.Builder(diffCallback).build() ) + /** + * Instantly submits a **new and fresh** list. This means that no changes like moves are done as + * we assume the new list is not the same thing as the old list, nothing is shared. + * + * The views are rendered instantly as a result, so no fade/pop-ins or similar. + * + * Use `submitList` for general use, as that can reuse old views. + * */ + open fun submitIncomparableList(list: List?) { + // This leverages a quirk in the submitList function that has a fast case for null arrays + // What this implies is that as long as we do a double submit we can ensure no pop-ins, + // as the changes are the entire list instead of calculating deltas + submitList(null) + submitList(list) + } + open fun submitList(list: List?) { // deep copy at least the top list, because otherwise adapter can go crazy mDiffer.submitList(list?.let { CopyOnWriteArrayList(it) }) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 7cdd5670566..cc4bd493551 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -90,25 +90,19 @@ open class ParentItemAdapter( if (binding !is HomepageParentBinding) return val info = item.list binding.apply { - homeChildRecyclerview.adapter = HomeChildItemAdapter( - fragment = fragment, - id = id + position + 100, - clickCallback = clickCallback, - nextFocusUp = homeChildRecyclerview.nextFocusUpId, - nextFocusDown = homeChildRecyclerview.nextFocusDownId, - ).apply { - isHorizontal = info.isHorizontalImages - hasNext = item.hasNext - submitList(item.list.list) - } - - /** We can reuse the HomeChildItemAdapter, but that causes weird a fade-in - * That is not really desirable. - * */ - /* val currentAdapter = homeChildRecyclerview.adapter as? HomeChildItemAdapter if (currentAdapter == null) { - ... + homeChildRecyclerview.adapter = HomeChildItemAdapter( + fragment = fragment, + id = id + position + 100, + clickCallback = clickCallback, + nextFocusUp = homeChildRecyclerview.nextFocusUpId, + nextFocusDown = homeChildRecyclerview.nextFocusDownId, + ).apply { + isHorizontal = info.isHorizontalImages + hasNext = item.hasNext + submitList(item.list.list) + } } else { currentAdapter.apply { isHorizontal = info.isHorizontalImages @@ -116,10 +110,9 @@ open class ParentItemAdapter( this.clickCallback = this@ParentItemAdapter.clickCallback nextFocusUp = homeChildRecyclerview.nextFocusUpId nextFocusDown = homeChildRecyclerview.nextFocusDownId - submitList(item.list.list) + submitIncomparableList(item.list.list) } - } - */ + } homeChildRecyclerview.setLinearListLayout( isHorizontal = true, From 2ca106ecb10d70ff2510dc1e34904945329b0586 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 5 Oct 2025 22:50:04 +0200 Subject: [PATCH 117/618] Fix: Recycle setMaxViewPoolSize on HomeChildItemAdapter for larger devices --- .../lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index cc4bd493551..a628d491e07 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -25,6 +25,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.PHONE import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.AppContextUtils.isRecyclerScrollable +import com.lagradost.cloudstream3.utils.AppContextUtils.setMaxViewPoolSize class LoadClickCallback( val action: Int = 0, @@ -103,6 +104,9 @@ open class ParentItemAdapter( hasNext = item.hasNext submitList(item.list.list) } + + // The vast majority of the lag comes from creating the view + homeChildRecyclerview.setMaxViewPoolSize(0, 20) } else { currentAdapter.apply { isHorizontal = info.isHorizontalImages From 716b809ea2879dc52c356d6650d03dda0edb0b77 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:40:00 +0200 Subject: [PATCH 118/618] Emergency patch for TV performance --- .../ui/home/HomeChildItemAdapter.kt | 11 +++ .../cloudstream3/ui/home/HomeFragment.kt | 73 +++++++++++++------ .../ui/home/HomeParentItemAdapter.kt | 11 ++- 3 files changed, 68 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt index 7ffb7ed0610..bb7ab8e710e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt @@ -5,9 +5,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.ImageView import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager import androidx.viewbinding.ViewBinding +import coil3.load import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.databinding.HomeRemoveGridBinding @@ -41,6 +43,15 @@ class HomeScrollViewHolderState(view: ViewBinding) : ViewHolderState(vi } } } + + override fun onViewRecycled() { + super.onViewRecycled() + + // Clear the image, idk if this saves ram or not, but I guess? + view.root.findViewById(R.id.imageView)?.apply { + load(null) + } + } } class ResumeItemAdapter( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 5d6cbfb358d..42601c8b01c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -399,16 +399,23 @@ class HomeFragment : Fragment() { val listView = dialog.findViewById(R.id.listview1) - val arrayAdapter = object : ArrayAdapter(this, R.layout.sort_bottom_single_provider_choice, + val arrayAdapter = object : ArrayAdapter( + this, R.layout.sort_bottom_single_provider_choice, mutableListOf() ) { - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.sort_bottom_single_provider_choice, parent, false) + override fun getView( + position: Int, + convertView: View?, + parent: ViewGroup + ): View { + val view = convertView ?: LayoutInflater.from(context) + .inflate(R.layout.sort_bottom_single_provider_choice, parent, false) val titleText = view.findViewById(R.id.text1) val pinIcon = view.findViewById(R.id.pinicon) val name = getItem(position) titleText?.text = name - val isPinned = pinnedphashset.contains(currentValidApis[position].name ?: "") + val isPinned = + pinnedphashset.contains(currentValidApis[position].name ?: "") pinIcon.visibility = if (isPinned) View.VISIBLE else View.GONE return view } @@ -420,7 +427,7 @@ class HomeFragment : Fragment() { if (currentValidApis.isNotEmpty()) { currentApiName = currentValidApis[i].name //to switch to apply simply remove this - currentApiName?.let(callback) + currentApiName.let(callback) dialog.dismissSafe() } } @@ -431,7 +438,11 @@ class HomeFragment : Fragment() { pinnedphashset = pinnedp.toHashSet() arrayAdapter.clear() val sortedApis = validAPIs - .filter {it.hasMainPage && (pinnedphashset.contains(it.name) || it.supportedTypes.any(preSelectedTypes::contains)) } + .filter { + it.hasMainPage && (pinnedphashset.contains(it.name) || it.supportedTypes.any( + preSelectedTypes::contains + )) + } .sortedBy { it.name.lowercase() } val sortedApiMap = LinkedHashMap().apply { @@ -459,12 +470,12 @@ class HomeFragment : Fragment() { } // pin provider on hold listView?.setOnItemLongClickListener { _, _, i, _ -> - if (currentValidApis.isNotEmpty() && i>1) { + if (currentValidApis.isNotEmpty() && i > 1) { val pinnedp = DataStoreHelper.pinnedProviders.toMutableList() val thisapi = currentValidApis[i].name - if(pinnedp.contains(thisapi)){ + if (pinnedp.contains(thisapi)) { pinnedp.remove(thisapi) - }else{ + } else { pinnedp.add(thisapi) } DataStoreHelper.pinnedProviders = pinnedp.toTypedArray() @@ -490,7 +501,7 @@ class HomeFragment : Fragment() { private val homeViewModel: HomeViewModel by activityViewModels() private val accountViewModel: AccountViewModel by activityViewModels() - fun addMovies(cards: List) { + fun addMovies(cards: List) { val ctx = context ?: run { Log.e(TAG, "Context is null, aborting addMovies") return @@ -515,6 +526,7 @@ class HomeFragment : Fragment() { Log.e(TAG, "Error adding movies: $e") } } + private fun deleteAll() { val ctx = context ?: run { Log.e(TAG, "Context is null, aborting deleteAll") @@ -599,6 +611,28 @@ class HomeFragment : Fragment() { private var bottomSheetDialog: BottomSheetDialog? = null private var homeMasterAdapter: HomeParentItemAdapterPreview? = null + var lastSavedHomepage: String? = null + + fun saveHomepageToTV(page : Map) { + // No need to update for phone + if(isLayout(PHONE)) { + return + } + val (name, data) = page.entries.firstOrNull() ?: return + // Modifying homepage is an expensive operation, and therefore we avoid it at all cost + if(name == lastSavedHomepage) { + return + } + Log.i(TAG, "Adding programs $name to TV") + lastSavedHomepage = name + ioSafe { + // empty the channel + deleteAll() + // insert the program from first array + addMovies(data.list.list) + } + } + @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -610,10 +644,10 @@ class HomeFragment : Fragment() { //homeChangeApiLoading.setOnClickListener(apiChangeClickListener) //homeChangeApiLoading.setOnClickListener(apiChangeClickListener) homeApiFab.setOnClickListener(apiChangeClickListener) - homeApiFab.setOnLongClickListener{ - if(currentApiName == noneApi.name) return@setOnLongClickListener false + homeApiFab.setOnLongClickListener { + if (currentApiName == noneApi.name) return@setOnLongClickListener false homeViewModel.loadAndCancel(currentApiName, forceReload = true, fromUI = true) - showToast(R.string.action_reload,Toast.LENGTH_SHORT) + showToast(R.string.action_reload, Toast.LENGTH_SHORT) true } homeChangeApi.setOnClickListener(apiChangeClickListener) @@ -628,7 +662,7 @@ class HomeFragment : Fragment() { } homeMasterAdapter = HomeParentItemAdapterPreview( fragment = this@HomeFragment, - homeViewModel,accountViewModel + homeViewModel, accountViewModel ) homeMasterRecycler.adapter = homeMasterAdapter //fixPaddingStatusbar(homeLoadingStatusbar) @@ -676,16 +710,7 @@ class HomeFragment : Fragment() { homeLoadingShimmer.stopShimmer() val d = data.value - val k = d.values.firstOrNull() - if (k != null) { - // empty the channel - deleteAll() - // insert the program from first array - addMovies(k.list.list) - } else { - Log.w("SafeAccess", "Map values are empty — cannot access first element") - } - + saveHomepageToTV(d) val mutableListOfResponse = mutableListOf() listHomepageItems.clear() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index a628d491e07..7c136839c02 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -49,6 +49,13 @@ open class ParentItemAdapter( a.list.list == b.list.list }) ) { + companion object { + // The vast majority of the lag comes from creating the view + // This simply shares the views between all HomeChildItemAdapter + private val sharedPool = + RecyclerView.RecycledViewPool().apply { this.setMaxRecycledViews(0, 20) } + } + data class ParentItemHolder(val binding: ViewBinding) : ViewHolderState(binding) { override fun save(): Bundle = Bundle().apply { val recyclerView = (binding as? HomepageParentBinding)?.homeChildRecyclerview @@ -93,6 +100,7 @@ open class ParentItemAdapter( binding.apply { val currentAdapter = homeChildRecyclerview.adapter as? HomeChildItemAdapter if (currentAdapter == null) { + homeChildRecyclerview.setRecycledViewPool(sharedPool) homeChildRecyclerview.adapter = HomeChildItemAdapter( fragment = fragment, id = id + position + 100, @@ -104,9 +112,6 @@ open class ParentItemAdapter( hasNext = item.hasNext submitList(item.list.list) } - - // The vast majority of the lag comes from creating the view - homeChildRecyclerview.setMaxViewPoolSize(0, 20) } else { currentAdapter.apply { isHorizontal = info.isHorizontalImages From 6611c8a6075aebc0c15e9945c4a22aa28b576c89 Mon Sep 17 00:00:00 2001 From: GCDarcy <104531578+gcdarcy@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:49:19 +0800 Subject: [PATCH 119/618] Added the option in settings for people to display the series/movie title instead of the name of the current source (partial support for languages) (#1956) --- .../cloudstream3/ui/player/GeneratorPlayer.kt | 20 +++++++++++-------- app/src/main/res/values-b+es/array.xml | 4 ++++ app/src/main/res/values-b+pl/array.xml | 6 +++++- app/src/main/res/values-b+tr/array.xml | 4 ++++ app/src/main/res/values-b+vi/array.xml | 4 ++++ app/src/main/res/values/array.xml | 4 ++++ app/src/main/res/values/strings.xml | 2 ++ 7 files changed, 35 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 4d72214a5c0..7370dea7588 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -1761,6 +1761,13 @@ class GeneratorPlayer : FullScreenPlayer() { } } + private fun getHeaderName(): String? { + return when (val meta = currentMeta) { + is ResultEpisode -> meta.headerName + is ExtractorUri -> meta.headerName + else -> null + } + } private fun getPlayerVideoTitle(): String { var headerName: String? = null var subName: String? = null @@ -1831,20 +1838,17 @@ class GeneratorPlayer : FullScreenPlayer() { @SuppressLint("SetTextI18n") fun setPlayerDimen(widthHeight: Pair?) { - val extra = if (widthHeight != null) { - val (width, height) = widthHeight - "- ${width}x${height}" - } else { - "" - } - + val extra = widthHeight?.let { (w, h) -> "- ${w}x${h}" } ?: "" val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name ?: "NULL" + val headerName = getHeaderName().orEmpty() val title = when (titleRez) { 0 -> "" 1 -> extra 2 -> source 3 -> "$source $extra" + 4 -> headerName + 5 -> "$headerName $extra" else -> "" } playerBinding?.playerVideoTitleRez?.apply { @@ -2172,4 +2176,4 @@ inline fun Bundle.getSafeSerializable(key: String): T if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) getSerializable(key) as? T else getSerializable( key, T::class.java - ) \ No newline at end of file + ) diff --git a/app/src/main/res/values-b+es/array.xml b/app/src/main/res/values-b+es/array.xml index 9c85a54045d..40a1be6e07f 100644 --- a/app/src/main/res/values-b+es/array.xml +++ b/app/src/main/res/values-b+es/array.xml @@ -15,6 +15,8 @@ + @string/resolution_and_name + @string/name @string/resolution_and_title @string/title @string/resolution @@ -22,6 +24,8 @@ + 5 + 4 3 2 1 diff --git a/app/src/main/res/values-b+pl/array.xml b/app/src/main/res/values-b+pl/array.xml index 708bab010d8..db9db9659a4 100644 --- a/app/src/main/res/values-b+pl/array.xml +++ b/app/src/main/res/values-b+pl/array.xml @@ -24,6 +24,8 @@ + @string/resolution_and_name + @string/name @string/resolution_and_title @string/title @string/resolution @@ -31,12 +33,14 @@ + 5 + 4 3 2 1 0 - + @string/none 16 znaków diff --git a/app/src/main/res/values-b+tr/array.xml b/app/src/main/res/values-b+tr/array.xml index ef99bd3897c..c4c4e4cda43 100644 --- a/app/src/main/res/values-b+tr/array.xml +++ b/app/src/main/res/values-b+tr/array.xml @@ -38,6 +38,8 @@ + @string/resolution_and_name + @string/name @string/resolution_and_title @string/title @string/resolution @@ -45,6 +47,8 @@ + 5 + 4 3 2 1 diff --git a/app/src/main/res/values-b+vi/array.xml b/app/src/main/res/values-b+vi/array.xml index f3404283972..dbbd0d3c9e8 100644 --- a/app/src/main/res/values-b+vi/array.xml +++ b/app/src/main/res/values-b+vi/array.xml @@ -16,6 +16,8 @@ + @string/resolution_and_name + @string/name @string/resolution_and_title @string/title @string/resolution @@ -23,6 +25,8 @@ + 5 + 4 3 2 1 diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index c436ab3b1ca..5440a1f0e6e 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -55,6 +55,8 @@ + @string/resolution_and_name + @string/name @string/resolution_and_title @string/title @string/resolution @@ -62,6 +64,8 @@ + 5 + 4 3 2 1 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6bef3cbca1f..0874e6a12a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -894,4 +894,6 @@ Remove watched up to this episode Reloaded Reload Provider + Name + Resolution and name From 6d935602526e8fdf67777018936abbc2a7716cba Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:12:46 +0200 Subject: [PATCH 120/618] Fix: Deduplicated subtitles --- .../cloudstream3/ui/player/CS3IPlayer.kt | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index d0461346b9a..e10d1db9732 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -100,6 +100,8 @@ import java.util.UUID import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession +import kotlin.collections.HashSet +import kotlin.text.StringBuilder const val TAG = "CS3ExoPlayer" const val PREFERRED_AUDIO_LANGUAGE_KEY = "preferred_audio_language" @@ -961,7 +963,8 @@ class CS3IPlayer : IPlayer { // Custom TextOutput to apply cue styling and rules to all subtitles val customTextOutput = TextOutput { cue -> // Do not remove filterNotNull as Java typesystem is fucked - val (bitmapCues, textCues) = cue.cues.filterNotNull().partition { it.bitmap != null } + val (bitmapCues, textCues) = cue.cues.filterNotNull() + .partition { it.bitmap != null } val styledBitmapCues = bitmapCues.map { bitmapCue -> bitmapCue @@ -971,16 +974,38 @@ class CS3IPlayer : IPlayer { .build() } + // Reuse memory, to avoid many allocations + val set = HashSet() + val buffer = StringBuilder() + // Move cues into one single one // This is to prevent text overlap in vtt (and potentially other) subtitle files val styledTextCues = textCues.groupBy { // Groups cues which share the same positon it.lineAnchor to it.position.times(1000.0f).toInt() }.mapNotNull { (_, entries) -> - val combinedCueText = entries.joinToString("\n") { - it.text?.toString() ?: "" + set.clear() + buffer.clear() + var count = 0 + for (x in entries) { + // Only allow non null text, otherwise we might have "a\n\nb" + val text = x.text ?: continue + + // Prevent duplicate entries, this often happens when the subtitle file + // uses multiple text lines as outlines. Most commonly found in fansubs + // with fancy subtitle styling. + if (!set.add(text)) { + continue + } + if (++count > 1) buffer.append('\n') + + // Trim to avoid weird formatting if the last line ends with a newline + buffer.append(text.trim()) } + val combinedCueText = buffer.toString() + + // Use the style of the first entry as the base entries .firstOrNull() ?.buildUpon() From bb77d93b5d133366bc43be6eb40336931884c48b Mon Sep 17 00:00:00 2001 From: Sovan Sinha Date: Tue, 7 Oct 2025 21:53:30 +0530 Subject: [PATCH 121/618] Enhancement : hide controls during volume/brightness gestures and restore on pause (#1954) * player: hide controls during volume/brightness gestures and restore on gesture end if paused * player: reshow controls only if visible pre-gesture; reveal immediately on gesture completion when paused --- .../ui/player/FullScreenPlayer.kt | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 9a1075f123b..08f34470ba2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -65,6 +65,8 @@ import com.lagradost.cloudstream3.ui.settings.Globals.PHONE import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.AppContextUtils.isUsingMobileData +import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback +import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute @@ -85,8 +87,6 @@ import kotlin.math.max import kotlin.math.min import kotlin.math.round import kotlin.math.roundToInt -import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback -import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking @@ -110,6 +110,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { // state of player UI protected var isShowing = false + private var uiShowingBeforeGesture = false protected var isLocked = false protected var hasEpisodes = false @@ -883,6 +884,13 @@ open class FullScreenPlayer : AbstractPlayerFragment() { delayHide() } + protected fun hidePlayerUI(){ + if (isShowing) { + isShowing = false + animateLayoutChanges() + } + } + override fun playerStatusChanged() { super.playerStatusChanged() delayHide() @@ -1202,6 +1210,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() { currentClickCount = 0 } + // If we hid the UI for a gesture and playback is paused, show it again + if (!player.getIsPlaying()) { + val didGesture = currentTouchAction != null || currentLastTouchAction != null + if (didGesture && uiShowingBeforeGesture && !isShowing) { + isShowing = true + animateLayoutChanges() + } + } + // call auto hide as it wont hide when you have your finger down autoHide() @@ -1213,6 +1230,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { currentTouchStartPlayerTime = null currentTouchLast = null currentTouchStartTime = null + uiShowingBeforeGesture = false // resets UI playerTimeText.isVisible = false @@ -1231,20 +1249,18 @@ open class FullScreenPlayer : AbstractPlayerFragment() { if (currentTouchAction == null) { val diffFromStart = startTouch - currentTouch - if (swipeVerticalEnabled) { if (abs(diffFromStart.y * 100 / screenHeightWithOrientation) > MINIMUM_VERTICAL_SWIPE) { // left = Brightness, right = Volume, but the UI is reversed to show the UI better + uiShowingBeforeGesture = isShowing currentTouchAction = if (startTouch.x < screenWidthWithOrientation / 2) { // hide the UI if you hold brightness to show screen better, better UX - if (isShowing) { - isShowing = false - animateLayoutChanges() - } - + hidePlayerUI() TouchAction.Brightness } else { + // hide the UI if you hold volume to show screen better, better UX + hidePlayerUI() TouchAction.Volume } } From 1f71acabcb691d53564b03dab85efe1dc93562fe Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:44:26 +0200 Subject: [PATCH 122/618] Fix: Single mirror syncdata --- .../actions/temp/PlayMirrorAction.kt | 5 +- .../ui/result/ResultViewModel2.kt | 77 ++++++++++++------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayMirrorAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayMirrorAction.kt index acc4d7308ef..d69619b45a1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayMirrorAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayMirrorAction.kt @@ -13,7 +13,6 @@ import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType -import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.list import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.txt @@ -53,11 +52,11 @@ class PlayMirrorAction : VideoClickAction() { return true } } - // Took logic from PLAY_EPISODE_IN_APP + activity.navigate( R.id.global_to_navigation_player, GeneratorPlayer.newInstance( - generatorMirror, list + generatorMirror, result.syncData ) ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 0a37a5b4115..183f60ff611 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -338,6 +338,7 @@ data class ResumeWatchingStatus( data class LinkLoadingResult( val links: List, val subs: List, + val syncData: HashMap ) sealed class SelectPopup { @@ -766,15 +767,19 @@ class ResultViewModel2 : ViewModel() { subs?.filter { subtitle -> downloadList.any { langTagIETF -> subtitle.languageCode == langTagIETF || - subtitle.originalName.contains(fromTagToEnglishLanguageName(langTagIETF) ?: langTagIETF) + subtitle.originalName.contains( + fromTagToEnglishLanguageName( + langTagIETF + ) ?: langTagIETF + ) } } - ?.map { ExtractorSubtitleLink(it.name, it.url, "", it.headers) } - ?.take(3) // max subtitles download hardcoded (?_?) - ?.forEach { link -> - val fileName = VideoDownloadManager.getFileName(context, meta) - downloadSubtitle(context, link, fileName, folder) - } + ?.map { ExtractorSubtitleLink(it.name, it.url, "", it.headers) } + ?.take(3) // max subtitles download hardcoded (?_?) + ?.forEach { link -> + val fileName = VideoDownloadManager.getFileName(context, meta) + downloadSubtitle(context, link, fileName, folder) + } } catch (e: Exception) { logError(e) } @@ -1415,7 +1420,11 @@ class ResultViewModel2 : ViewModel() { _loadedLinks.postValue(null) } - return LinkLoadingResult(sortUrls(links), sortSubs(subs)) + return LinkLoadingResult( + sortUrls(links), + sortSubs(subs), + HashMap(currentResponse?.syncData ?: emptyMap()) + ) } fun handleAction(click: EpisodeClickEvent) = @@ -1427,16 +1436,23 @@ class ResultViewModel2 : ViewModel() { _episodeSynopsis.postValue(null) } - private fun markEpisodes(editor: Editor,episodeIds: Array,watchState: VideoWatchState) { + private fun markEpisodes( + editor: Editor, + episodeIds: Array, + watchState: VideoWatchState + ) { val watchStateString = DataStore.mapper.writeValueAsString(watchState) episodeIds.forEach { - if(getVideoWatchState(it.toInt()) != watchState){ - editor.setKeyRaw(getFolderName("$currentAccount/$VIDEO_WATCH_STATE", it),watchStateString) + if (getVideoWatchState(it.toInt()) != watchState) { + editor.setKeyRaw( + getFolderName("$currentAccount/$VIDEO_WATCH_STATE", it), + watchStateString + ) } } } - private fun getEpisodesIdsBySeason(season: Int): HashMap> { + private fun getEpisodesIdsBySeason(season: Int): HashMap> { val result = currentEpisodes.entries .asSequence() .filter { it.key.season <= season && it.key.dubStatus == preferDubStatus } @@ -1447,7 +1463,7 @@ class ResultViewModel2 : ViewModel() { .mapValues { (_, ids) -> ids.toTypedArray() } .toMap(HashMap()) - if(season != 0){ + if (season != 0) { result.remove(0) } return result @@ -1490,8 +1506,9 @@ class ResultViewModel2 : ViewModel() { val watchedText = if (isWatched) R.string.action_remove_from_watched else R.string.action_mark_as_watched - val markUpToText = if(isWatched) R.string.action_remove_mark_watched_up_to_this_episode - else R.string.action_mark_watched_up_to_this_episode + val markUpToText = + if (isWatched) R.string.action_remove_mark_watched_up_to_this_episode + else R.string.action_mark_watched_up_to_this_episode options.add(txt(watchedText) to ACTION_MARK_AS_WATCHED) @@ -1643,9 +1660,8 @@ class ResultViewModel2 : ViewModel() { } ACTION_PLAY_EPISODE_IN_PLAYER -> { - val data = currentResponse?.syncData?.toList() ?: emptyList() - val list = - HashMap().apply { putAll(data) } + val list = HashMap(currentResponse?.syncData ?: emptyMap()) + generator?.also { it.getAll() // I know kinda shit to iterate all, but it is 100% sure to work ?.indexOfFirst { value -> value is ResultEpisode && value.id == click.data.id } @@ -1682,18 +1698,22 @@ class ResultViewModel2 : ViewModel() { reloadEpisodes() } - ACTION_MARK_WATCHED_UP_TO_THIS_EPISODE -> ioSafe{ - val editor = context?.let { it1 -> editor(it1,false) } + ACTION_MARK_WATCHED_UP_TO_THIS_EPISODE -> ioSafe { + val editor = context?.let { it1 -> editor(it1, false) } if (editor != null) { - val (clickSeason,clickEpisode) = click.data.let { (it.season ?: 0) to it.episode } - val watchState = if (getVideoWatchState(click.data.id) == VideoWatchState.Watched) VideoWatchState.None else VideoWatchState.Watched - val seasons = getEpisodesIdsBySeason(clickSeason) + val (clickSeason, clickEpisode) = click.data.let { + (it.season ?: 0) to it.episode + } + val watchState = + if (getVideoWatchState(click.data.id) == VideoWatchState.Watched) VideoWatchState.None else VideoWatchState.Watched + val seasons = getEpisodesIdsBySeason(clickSeason) - seasons.keys.forEach {currentSeason -> + seasons.keys.forEach { currentSeason -> var episodeIds = seasons[currentSeason] ?: emptyArray() - if(currentSeason == clickSeason) episodeIds = episodeIds.sliceArray(0 until clickEpisode) - markEpisodes(editor,episodeIds,watchState) + if (currentSeason == clickSeason) episodeIds = + episodeIds.sliceArray(0 until clickEpisode) + markEpisodes(editor, episodeIds, watchState) } editor.apply() reloadEpisodes() @@ -1992,7 +2012,10 @@ class ResultViewModel2 : ViewModel() { return when (sorting) { EpisodeSortType.NUMBER_ASC -> episodes.sortedBy { it.episode } EpisodeSortType.NUMBER_DESC -> episodes.sortedByDescending { it.episode } - EpisodeSortType.RATING_HIGH_LOW -> episodes.sortedByDescending { it.score?.toDouble() ?: 0.0 } + EpisodeSortType.RATING_HIGH_LOW -> episodes.sortedByDescending { + it.score?.toDouble() ?: 0.0 + } + EpisodeSortType.RATING_LOW_HIGH -> episodes.sortedBy { it.score?.toDouble() ?: 0.0 } EpisodeSortType.DATE_NEWEST -> episodes.sortedByDescending { it.airDate } EpisodeSortType.DATE_OLDEST -> episodes.sortedBy { it.airDate } From b63990da38cdb6d084a0bf2ab5bd554da526ebe3 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:54:33 +0200 Subject: [PATCH 123/618] Fix: Hero banner shown in None. Closes #1957 --- .../cloudstream3/ui/home/HomeParentItemAdapterPreview.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index c2f06c0210b..9df98e439f0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -624,6 +624,9 @@ class HomeParentItemAdapterPreview( previewViewpager.isVisible = true previewViewpagerText.isVisible = true alternativeAccountPadding?.isVisible = false + (binding as? FragmentHomeHeadTvBinding)?.apply { + homePreviewInfoBtt.isVisible = true + } } else -> { @@ -632,6 +635,9 @@ class HomeParentItemAdapterPreview( previewViewpager.isVisible = false previewViewpagerText.isVisible = false alternativeAccountPadding?.isVisible = true + (binding as? FragmentHomeHeadTvBinding)?.apply { + homePreviewInfoBtt.isVisible = false + } //previewHeader.isVisible = false } } From 3a56d6d36c3a96eb850f49b52737b561bd60f3e4 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Wed, 8 Oct 2025 01:11:15 +0200 Subject: [PATCH 124/618] Feat(UI): New speed dialog, Closes #1889 --- .../ui/player/FullScreenPlayer.kt | 108 +++++++++---- app/src/main/res/layout/speed_dialog.xml | 145 ++++++++++++++++++ 2 files changed, 219 insertions(+), 34 deletions(-) create mode 100644 app/src/main/res/layout/speed_dialog.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 08f34470ba2..f6f68eaf282 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -5,6 +5,7 @@ import android.annotation.SuppressLint import android.app.Activity import android.app.Dialog import android.content.Context +import android.content.DialogInterface import android.content.pm.ActivityInfo import android.content.res.ColorStateList import android.content.res.Configuration @@ -33,6 +34,7 @@ import android.view.animation.Animation import android.view.animation.AnimationUtils import android.widget.LinearLayout import androidx.annotation.OptIn +import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.graphics.blue import androidx.core.graphics.green @@ -56,6 +58,7 @@ import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding +import com.lagradost.cloudstream3.databinding.SpeedDialogBinding import com.lagradost.cloudstream3.databinding.SubtitleOffsetBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive @@ -68,7 +71,6 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.getNavigationBarHeight @@ -491,7 +493,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { activity?.attachBackPressedCallback("FullScreenPlayer") { if (isShowingEpisodeOverlay) { // isShowingEpisodeOverlay pauses, so this makes it easier to unpause - if(isLayout(TV or EMULATOR)) { + if (isLayout(TV or EMULATOR)) { playerPausePlay?.requestFocus() } toggleEpisodesOverlay(show = false) @@ -655,39 +657,76 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } + @SuppressLint("SetTextI18n") + fun updateSpeedDialogBinding(binding: SpeedDialogBinding) { + val speed = player.getPlaybackSpeed() + binding.speedText.text = "%.2fx".format(speed).replace(".0x", "x") + // Android crashes if you don't round to an exact step size + binding.speedBar.value = (speed.coerceIn(0.1f, 2.0f) / binding.speedBar.stepSize).roundToInt().toFloat() * binding.speedBar.stepSize + } + private fun showSpeedDialog() { - val speedsText = - listOf( - "0.5x", - "0.75x", - "0.85x", - "1x", - "1.15x", - "1.25x", - "1.4x", - "1.5x", - "1.75x", - "2x" - ) - val speedsNumbers = - listOf(0.5f, 0.75f, 0.85f, 1f, 1.15f, 1.25f, 1.4f, 1.5f, 1.75f, 2f) - val speedIndex = speedsNumbers.indexOf(player.getPlaybackSpeed()) - - activity?.let { act -> - act.showDialog( - speedsText, - speedIndex, - act.getString(R.string.player_speed), - false, - { - if (isFullScreenPlayer) - activity?.hideSystemUI() - }) { index -> - if (isFullScreenPlayer) - activity?.hideSystemUI() - setPlayBackSpeed(speedsNumbers[index]) + val act = activity ?: return + val isPlaying = player.getIsPlaying() + player.handleEvent(CSPlayerEvent.Pause, PlayerEventSource.UI) + + val binding: SpeedDialogBinding = SpeedDialogBinding.inflate( + LayoutInflater.from(act) + ) + + updateSpeedDialogBinding(binding) + for ((view, speed) in arrayOf( + binding.speed25 to 0.25f, + binding.speed100 to 1.0f, + binding.speed125 to 1.25f, + binding.speed150 to 1.5f, + binding.speed200 to 2.0f, + )) { + view.setOnClickListener { + setPlayBackSpeed(speed) + updateSpeedDialogBinding(binding) } } + + binding.speedMinus.setOnClickListener { + setPlayBackSpeed(maxOf((player.getPlaybackSpeed() - 0.1f), 0.1f)) + updateSpeedDialogBinding(binding) + } + + binding.speedPlus.setOnClickListener { + setPlayBackSpeed(minOf((player.getPlaybackSpeed() + 0.1f), 2.0f)) + updateSpeedDialogBinding(binding) + } + + binding.speedBar.addOnChangeListener { slider, value, fromUser -> + if (fromUser) { + setPlayBackSpeed(value) + updateSpeedDialogBinding(binding) + } + } + + val dismiss = DialogInterface.OnDismissListener { + if (isFullScreenPlayer) + activity?.hideSystemUI() + if (isPlaying) { + player.handleEvent(CSPlayerEvent.Play, PlayerEventSource.UI) + } + } + + //if (isLayout(PHONE)) { + // val builder = + // BottomSheetDialog(act, R.style.AlertDialogCustom) + // builder.setContentView(binding.root) + // builder.setOnDismissListener(dismiss) + // builder.show() + //} else { + val builder = + AlertDialog.Builder(act, R.style.AlertDialogCustom) + .setView(binding.root) + builder.setOnDismissListener(dismiss) + val dialog = builder.create() + dialog.show() + //} } fun resetRewindText() { @@ -884,7 +923,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { delayHide() } - protected fun hidePlayerUI(){ + protected fun hidePlayerUI() { if (isShowing) { isShowing = false animateLayoutChanges() @@ -1212,7 +1251,8 @@ open class FullScreenPlayer : AbstractPlayerFragment() { // If we hid the UI for a gesture and playback is paused, show it again if (!player.getIsPlaying()) { - val didGesture = currentTouchAction != null || currentLastTouchAction != null + val didGesture = + currentTouchAction != null || currentLastTouchAction != null if (didGesture && uiShowingBeforeGesture && !isShowing) { isShowing = true animateLayoutChanges() diff --git a/app/src/main/res/layout/speed_dialog.xml b/app/src/main/res/layout/speed_dialog.xml new file mode 100644 index 00000000000..bb97914f1c8 --- /dev/null +++ b/app/src/main/res/layout/speed_dialog.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 0281eb418561b28effe39c6576f679c8d480b95a Mon Sep 17 00:00:00 2001 From: KingLucius Date: Fri, 10 Oct 2025 20:27:53 +0300 Subject: [PATCH 125/618] PercentageCropImageView KDoc and XML usage (#1970) --- .../cloudstream3/ui/home/HomeScrollAdapter.kt | 2 - .../ui/result/ResultFragmentTv.kt | 2 - .../utils/PercentageCropImageView.kt | 75 ++++++++++++++++++- .../main/res/layout/fragment_result_tv.xml | 1 + .../main/res/layout/home_scroll_view_tv.xml | 2 + app/src/main/res/values/attrs.xml | 5 ++ 6 files changed, 81 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index 3a6ed4923f2..7c87542afe3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -59,8 +59,6 @@ class HomeScrollAdapter( } is HomeScrollViewTvBinding -> { - //Change poster crop area to 20% from Top - binding.homeScrollPreview.cropYCenterOffsetPct = 0.2f binding.homeScrollPreview.isFocusable = false binding.homeScrollPreview.setOnClickListener { view -> callback.invoke(view ?: return@setOnClickListener, position, item) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index bb6c28f0bbd..cacf0294280 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -939,8 +939,6 @@ class ResultFragmentTv : Fragment() { R.drawable.profile_bg_red, R.drawable.profile_bg_teal ).random() - //Change poster crop area to 20% from Top - backgroundPoster.cropYCenterOffsetPct = 0.20F backgroundPoster.loadImage(d.posterBackgroundImage) { error { getImageFromDrawable(context ?: return@error null, error) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt index 1e572fb7c3e..5f070ef065a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt @@ -4,16 +4,65 @@ import android.content.Context import android.graphics.Matrix import android.graphics.drawable.Drawable import android.util.AttributeSet +import com.lagradost.cloudstream3.R +/** + * A custom [AppCompatImageView] that allows precise control over the visible crop area + * of an image by adjusting its horizontal and vertical center offset percentages. + * + * ### Key Features: + * - Allows **manual vertical or horizontal cropping** via percentage offsets. + * - Works seamlessly with Coil, Glide, or any image loading library. + * + * ### Usage (XML): + * You can set the crop offset directly in XML using custom attributes: + * ```xml + * + * ``` + * - `app:cropYCenterOffsetPct` → controls how far vertically the image shifts + * `0.0` = top-aligned, `0.5` = centered, `1.0` = bottom-aligned. + * - `app:cropXCenterOffsetPct` → controls how far horizontally the image shifts + * `0.0` = left, `0.5` = center, `1.0` = right. + * + * ### Programmatic Example: + * ```kotlin + * imageView.cropYCenterOffsetPct = 0.15f // Show slightly more (15%) of the top area + * imageView.cropXCenterOffsetPct = 0.5f // Keep image centered horizontally + * imageView.redraw() //Only needed if you changed cropYCenterOffsetPct/cropXCenterOffsetPct at runtime + * ``` + * + * ### Notes: + * - Must use `android:scaleType="matrix"` to enable manual matrix transformations. + * - Reference: https://stackoverflow.com/a/29055283 + * + * @property cropYCenterOffsetPct the vertical crop percentage (0.0–1.0) + * @property cropXCenterOffsetPct the horizontal crop percentage (0.0–1.0) + * + * @see ImageView.ScaleType.MATRIX + */ class PercentageCropImageView : androidx.appcompat.widget.AppCompatImageView { private var mCropYCenterOffsetPct: Float? = null private var mCropXCenterOffsetPct: Float? = null + constructor(context: Context?) : super(context!!) - constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) + + constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) { + initAttrs(context, attrs) + } + constructor( context: Context?, attrs: AttributeSet?, defStyle: Int - ) : super(context!!, attrs, defStyle) + ) : super(context!!, attrs, defStyle) { + initAttrs(context, attrs) + } var cropYCenterOffsetPct: Float get() = mCropYCenterOffsetPct!! @@ -80,6 +129,7 @@ class PercentageCropImageView : androidx.appcompat.widget.AppCompatImageView { super.setImageResource(resId) myConfigureBounds() } + // In case you can change the ScaleType in code you have to call redraw() //fullsizeImageView.setScaleType(ScaleType.FIT_CENTER); //fullsizeImageView.redraw(); @@ -91,4 +141,25 @@ class PercentageCropImageView : androidx.appcompat.widget.AppCompatImageView { setImageDrawable(d) } } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { + attrs ?: return + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.PercentageCropImageView) + try { + if (typedArray.hasValue(R.styleable.PercentageCropImageView_cropYCenterOffsetPct)) { + mCropYCenterOffsetPct = typedArray.getFloat( + R.styleable.PercentageCropImageView_cropYCenterOffsetPct, + 0.5f + ) + } + if (typedArray.hasValue(R.styleable.PercentageCropImageView_cropXCenterOffsetPct)) { + mCropXCenterOffsetPct = typedArray.getFloat( + R.styleable.PercentageCropImageView_cropXCenterOffsetPct, + 0.5f + ) + } + } finally { + typedArray.recycle() + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 57624bbf1c5..aa674c44ba2 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -33,6 +33,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_gravity="center" android:alpha="0.8" android:scaleType="matrix" + app:cropYCenterOffsetPct="0.20" tools:src="@drawable/profile_bg_dark_blue" /> @@ -10,6 +11,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="matrix" + app:cropYCenterOffsetPct="0.20" tools:src="@drawable/example_poster" /> + + + + + \ No newline at end of file From 141a1a2adb9ec4ca15ee22a1d7a6c25a5f362d66 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 10 Oct 2025 19:28:06 +0200 Subject: [PATCH 126/618] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (818 of 818 strings) Translated using Weblate (Spanish) Currently translated at 99.5% (814 of 818 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (818 of 818 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (818 of 818 strings) Translated using Weblate (Polish) Currently translated at 100.0% (818 of 818 strings) Translated using Weblate (Czech) Currently translated at 100.0% (818 of 818 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Malay) Currently translated at 74.5% (608 of 816 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.8% (807 of 816 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Turkish) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Macedonian) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Arabic (Levantine)) Currently translated at 99.1% (809 of 816 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.7% (806 of 816 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Spanish) Currently translated at 99.5% (812 of 816 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Polish) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Czech) Currently translated at 100.0% (816 of 816 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Vietnamese) Currently translated at 100.0% (815 of 815 strings) Co-authored-by: Adrián Gelmotto Ruiz Co-authored-by: Dört Koldan Taciz Co-authored-by: Fjuro Co-authored-by: Hosted Weblate Co-authored-by: Jimly Asshiddiqy Co-authored-by: Lenny Tran Co-authored-by: Matthaiks Co-authored-by: Mohammad Abdallah Co-authored-by: Oğuz Ersen Co-authored-by: Saúl Palacios Co-authored-by: diogob003 Co-authored-by: kerklangsi Co-authored-by: stojkovskistefan Co-authored-by: Максим Горпиніч Co-authored-by: 大王叫我来巡山 Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/apc/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/cs/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/es/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/id/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/mk/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ms/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pl/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/tr/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/uk/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/vi/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hans/ Translation: Cloudstream/App --- app/src/main/res/values-b+apc/strings.xml | 8 +- app/src/main/res/values-b+cs/strings.xml | 5 +- app/src/main/res/values-b+es/strings.xml | 9 +- app/src/main/res/values-b+in/strings.xml | 3 +- app/src/main/res/values-b+mk/strings.xml | 7 +- app/src/main/res/values-b+ms/strings.xml | 34 +++- app/src/main/res/values-b+pl/strings.xml | 5 +- app/src/main/res/values-b+pt+BR/strings.xml | 189 ++++++++++---------- app/src/main/res/values-b+tr/strings.xml | 9 +- app/src/main/res/values-b+uk/strings.xml | 5 +- app/src/main/res/values-b+vi/strings.xml | 6 +- app/src/main/res/values-b+zh/strings.xml | 5 +- 12 files changed, 166 insertions(+), 119 deletions(-) diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index c6a429fb771..0e7a88ab0b7 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -716,9 +716,13 @@ حجم الپوستر دايمًا سئالوني خليك كابس حتى تسرع - خليك كابس كرمال صرعة الـ2x + خليك كابس كرمال سرعة الـ2x %1$dساعة %2$dد %3$dث %1$dد %2$dث %1$dث لايبل الرايتينگ - \ No newline at end of file + لا يوجد حساب + عدّل صورة الملف + ادخل لينك ( عنوان ال URL ) تبع صورة الملف + تم تعديل الصورة بنجاح + diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index f58b65859f6..e06b0cbbe46 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -750,4 +750,7 @@ Odebrat zhlédnutí po tuto epizodu Znovu načteno Znovu načíst poskytovatele - \ No newline at end of file + Přehrát ze zrcadla" + Název + Rozlišení a název + diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index 98adc1bec56..0209824e139 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -700,7 +700,7 @@ Preguntar siempre Descargas en paralelo Conexiones concurrentes - Cuántas conexiones concurrentes para cada descarga se pueden usar + Cuántas conexiones concurrentes para cada descarga se pueden usar durante un proceso de descarga Ir a Descargas Sin conexión a internet. \n\nConéctese a internet y vuelva a intentarlo, o mire sus descargas mientras está sin conexión. Cambios en los límites de la pantalla @@ -719,4 +719,9 @@ Marcar como vigilado en este episodio Retirar vigilado para este episodio Recargado - \ No newline at end of file + Reproducir en espejo" + Editar imagen de perfil + Introducir URL de imagen de perfil + Recargar proveedor + Nombre + diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index 75e544b30c1..e435f5c96fd 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -746,4 +746,5 @@ Hapus penandaan telah ditonton hingga episode ini Dimuat Ulang Muat Ulang Penyedia - \ No newline at end of file + Putar mirror" + diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index 9c1cddd0f8c..47807726076 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -235,7 +235,7 @@ Изглед на емулатор Видео Исчисти - Положен + Успешна верификација Име на сајт Неважечки податоци Поддршка @@ -401,7 +401,7 @@ Прикажан плеер - Барај износ Андроид ТВ Не успеа да ги врати податоците од датотеката %s - Не успеа + Неуспешна верификација Документарец Мрежен проток %d мин @@ -702,4 +702,5 @@ Избриши изгледано до оваа епизода Повторно вчитано Повторно вчитај провајдер - \ No newline at end of file + Пушти друг линк" + diff --git a/app/src/main/res/values-b+ms/strings.xml b/app/src/main/res/values-b+ms/strings.xml index bd4462e21ef..77532da7db3 100644 --- a/app/src/main/res/values-b+ms/strings.xml +++ b/app/src/main/res/values-b+ms/strings.xml @@ -106,7 +106,7 @@ Sambung muat turun Storan dalaman Carian mengikut sumber - %d Benenes yang diberikan kepada pemaju + %d Pisang yang diberikan kepada pemaju Saiz Teks Mohon Warna teks @@ -118,7 +118,7 @@ Sambung tonton Buang Lebih Maklumat - Sumber ini adalah torrent, penggunaan VPN disyorkan + Sumber ini adalah torrent, penggunaan VPN digalakkan Metadata tidak diberikan oleh laman web, video lalai akan gagala sekiranya tidak wujud di laman web. Penerangan Papar Logcat🐈 @@ -137,13 +137,13 @@ Pilih semua Nyahpilih semua VPN mungkin diperlukan untuk sumber ini berjalan dengan lancar - Sambung pasang dalam pemain mini di atas aplikasi lain + Sambung Rakaman didalam pemain mini di atas aplikasi lain Sari kata Tetapan sari kata pemain Chromecast Sari kata Kelajuan pemain Auto-pasang episod seterusnya - Gunakan terang system + Gunakan keterangan sistem Cuba sambungan talian… Informasi Lihat info ralat @@ -401,7 +401,7 @@ Beri hasil carian dipisah mengikut pelbagai sumber Aplikasi Light novel oleh pembangun sama Aplikasi Anime oleh pembangun sama - Masuk Discord + Menyertai Discord Padam Fail Padam (%1$d | %2$s) Muatkan ke skrin @@ -523,4 +523,26 @@ Gagal pulihkan data dari fail %s Ralat sandaran %s Hanya hantar data apabila mengalami kegagalan - \ No newline at end of file + + Episode + + Akan datang pada %s + Ini akan memadamkan secara kekal %s\nAdakah anda pasti? + Adakah anda pasti mahu memadamkan item berikut secara kekal?\n\n%s + Adakah anda pasti mahu memadamkan episod berikut secara kekal %1$s?\n\n%2$s + Anda juga akan memadamkan semua episod dalam siri berikut secara kekal:\n\n%s + Adakah anda pasti mahu memadamkan semua episod dalam siri berikut secara kekal?\n\n%s + %s\nyang tinggal + Sedang berlangsung + Penilaian + Filem + Torrents + Dokumentari + Siaran Langsung + Movie + Torrent + Dokumentari + Siaran Langsung + Audio + Podcast + diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index 2326d9d8f15..4d153a03169 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -727,4 +727,7 @@ Oznacz jako obejrzane do tego odcinka Przeładowano Przeładuj dostawcę - \ No newline at end of file + Odtwarzaj inne źródło" + Nazwa + Rozdzielczość i nazwa + diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index 688dbfcad96..0557b53995b 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -12,16 +12,15 @@ Poster Pôster Pôster do episódio - Pôster Principal + Pôster principal Próximo Aleatório Voltar - Alterar Provedor + Alterar provedor Visualizar plano de fundo Velocidade (%.2fx) Avaliado: %.1f - Nova atualização encontrada! -\n%1$s -> %2$s + Nova atualização encontrada! \n%1$s → %2$s Preenchimento %d min CloudStream @@ -42,8 +41,8 @@ Assistindo Em espera Concluído - Desistido - Planejando assistir + Desisti de ver + Planejo assistir Reassistindo Reproduzir filme Transmitir Torrent @@ -54,7 +53,7 @@ Reproduzir episódio Download - Transferido + Baixado Baixando Download pausado Download iniciado @@ -65,20 +64,20 @@ Erro ao carregar links Armazenamento interno Dub - Sub - Deletar arquivo + Leg + Apagar arquivo Reproduzir arquivo - Retomar download + Continuar download Pausar download Desative o relatório automático de erros Mais informações - Esconder + Ocultar Reproduzir Informações Filtrar marcadores Marcadores Remover - Definir como assistido/não assistido + Adicionar a lista Aplicar Copiar Fechar @@ -88,7 +87,7 @@ Configurações de legendas Cor do texto Cor do contorno - Cor de fundo + Cor do fundo Cor da janela Tipo de borda Elevação da legenda @@ -98,14 +97,14 @@ Pesquisar usando tipos %d Benenes doados aos desenvolvedores Nenhuma Benenes doada - Seleção automática de idioma - Baixar idiomas - Idioma da Legenda + Escolher automaticamente idioma + Salvar idiomas ao baixar + Idioma da legenda Segure para redefinir para o padrão Importe fontes colocando-as em %s Continuar assistindo Remover - Mais Info + Mais detalhes @string/home_play Uma VPN pode ser necessária para esse fornecedor funcionar corretamente Esse fornecedor é um torrent, uma VPN é recomendada @@ -115,16 +114,16 @@ Descrição não encontrada Mostrar Logcat 🐈 Picture-in-picture - Continua a reprodução em um player miniatura que sobrepõe outros aplicativos + Continua o vídeo em tamanho miniatura flutuando sobre outros aplicativos Redimensionar player Remover bordas pretas - Legendas - Configurações de legendas do Player - Legendas do Chromecast - Configurações de legendas do Chromecast - Velocidade de playback + Modificar legendas + Alterar legendas exibidas no player de vídeo + Modificar legendas do Chromecast + Alterar legendas exibidas no Chromecast + Velocidade do vídeo Deslize para avançar o vídeo - Deslize de lado à lado para controlar a posição no vídeo + Deslizar dedo para os lados para voltar ou avançar o vídeo Deslize para mudar as configurações Deslize para cima ou para baixo, para ajustar brilho ou volume Toque duplo para avançar o vídeo @@ -143,54 +142,53 @@ Dados salvos Permissões de armazenamento faltando. Por favor tente novamente. Erro no backup de %s - Procurar + Pesquisar Contas e Segurança Atualizações e Backup Info - Procura Avançada + Pesquisa avançada Mostrar resultados separados por fornecedor - Só enviar dados sobre travamentos + Enviar apenas dados de falhas Não enviar nenhum dado - Mostrar episódios de Filler em anime + Mostrar episódios extras de animes Mostrar trailers Mostrar posters do Kitsu Esconder qualidades de vídeo selecionadas nos resultados da pesquisa - Atualizações de plugin automáticas + Atualização automática de plugins Mostrar atualizações do app Automaticamente procurar por novas atualizações ao abrir. - Atualizar para pré-lançamento - Procura atualizações do pré-lançamento ao invés de apenas do lançamento oficial - Github - App de Light novel pelos mesmos desenvolvedores - App de Anime pelos mesmos desenvolvedores - Junte-se ao Discord - Dar um benene para os desenvolvedores + Permitir versões em pré-lançamento + Permitir a instação de versões em pré-lançamento ao invés de somente versões estáveis + GitHub + App de Light novel criado pelos mesmos desenvolvedores + App de Anime criado pelos mesmos desenvolvedores + Entre no servidor do Discord + Dar um benene aos desenvolvedores Benene dada Idioma do aplicativo Esse fornecedor não possui suporte para Chromecast Nenhum link encontrado Link copiado para área de transferência - Assistir Episódio + Assistir episódio Restaurar para o padrão - Desculpe, a aplicação travou. Um relatório de erro anônimo será enviado para os desenvolvedores + Desculpe :/, o aplicativo travou. Um relatório de erro anônimo será enviado aos desenvolvedores Temporada - Nenhuma Temporada + Nenhuma temporada Episódio Episódios - S + T E - Nenhum Episódio encontrado - Apagar Arquivo - Deletar + Nenhum episódio encontrado + Apagar arquivo + Apagar Cancelar Pausar - Retomar + Continuar -30 +30 Isso apagará %s permanentemente \nVocê tem certeza? - %dm -\nsobrando + %dm\nrestantes Em andamento Concluído Estado @@ -200,15 +198,15 @@ Site Sinopse Na fila - Sem Legendas + Sem legendas Padrão Livre Usado - App + CloudStream Filmes Séries - Desenhos Animados + Desenhos animados Anime Torrents Documentários @@ -226,48 +224,48 @@ Documentário Drama Asiático Transmissão ao vivo - Erro de fornecimento - Erro remoto + Erro ao abrir arquivo + Erro no servidor Erro de renderização - Erro de player inesperado + Erro inesperado no player Erro ao baixar, verifique as permissões de armazenamento Episódio pelo Chromecast Alternativa pelo Chromecast Assistir no App Assistir no %s - Auto download - Baixar por servidor alternativo + Baixar primeiro disponível + Escolher onde baixar Recarregar links Baixar legendas - Etiqueta de qualidade - Etiqueta Dub - Etiqueta Sub + Indicação de qualidade do vídeo + Indicação Dub + Indicação Sub Título - Alternar elementos da interface no pôster - Nenhuma Atualização encontrada - Procurar nova Atualização - Bloquear - Mudar Tamanho + Escolher o que mostrar sobre o pôster + Nenhuma atualização encontrada + Procurar atualização + Bloquear toques + Redimensionar Fonte - Pular Abertura + Pular abertura Não mostrar de novo - Pular essa Atualização + Pular esta atualização Atualizar Qualidade preferida de reprodução (Wi-fi) Máximo de caracteres do título de vídeos Resolução do player de vídeo Tamanho do buffer do vídeo - Comprimento do buffer do vídeo + Duração do buffer do vídeo Cache do vídeo em disco Limpar cache de vídeo e imagem Causará travamentos se o valor escolhido for muito alto em dispositivos com pouca memória RAM, como um Android TV. Causa problemas em sistemas com pouco espaço de armazenamento se definido muito alto, como em dispositivos Android TV. - DNS sobre HTTPS + DNS através de HTTPS Útil para burlar bloqueios de provedores de internet - Clonar site + Adicionar site alternativo Remover site Adiciona um clone de um site existente, com uma URL diferente - Caminho para Download + Onde salvar Downloads URL do servidor NGINX Mostrar Anime Dublado/Legendado Ajustar para a Tela @@ -444,7 +442,7 @@ Log do Teste Baixar plugins automaticamente Selecione o modo para filtrar os plugins baixados - Teste falhou + Reprovou nos testes A Barra de Progresso pode ser usada quando o player estiver visível Organizar Sim @@ -458,7 +456,7 @@ Alfabética(A => Z) Abrir com Selecionar Biblioteca - Passou + Passou nos testes Sua biblioteca está vazia :0 \nEntre numa conta de biblioteca ou adicione Midias para sua biblioteca local. Qualidade preferida de reprodução (Dados Móveis) @@ -470,7 +468,7 @@ Atualização iniciada Conteúdo +18 Ajuda - Processo de configuração de Redo + Refazer configurações iniciais Não foi possível instalar a nova versão do aplicativo instalador de pacotes Organizar por @@ -491,7 +489,7 @@ Episódio %d lançado! Selecionar padrão Inscrição cancelada de %s - Alguns aparelhos não possuem suporte para este pacote de instalação. Tente a opção legada se a atualização não instalar. + Alguns aparelhos não permitem instalação automática. Tente a opção legada (manual) se a atualização falhar. Dados móveis Perfil %d Atualizando shows inscritos @@ -533,7 +531,7 @@ Limpar historico Tem Muito texto. Não é possível salvar no clipboard. Player de vídeo preferido - Começar + Iniciar Suportado Status Abrindo mistura @@ -546,7 +544,7 @@ 18+ Links Funcionalidades do Player - Instalador APK + Instalador de APK Aparência Desativar Usar @@ -607,7 +605,7 @@ Notificação de novo episódio Pesquisar em outras extensões Mostrar recomendações - Adiciona uma opção de velocidade no reprodutor + Mostrar botão de velocidade do vídeo Testar todas as extensões Esse teste é feito somente para desenvolvedores e não verifica ou nega o funcionamento de qualquer extensão. Desbloquear CloudStream @@ -634,8 +632,8 @@ Áudio-livro Mídia Redefinir - Próximos em %s - Temporada %1$d Episódio %2$d será lançado em + Próximo em %s + %2$dº episódio da %1$dª temporada estreia em Selecione o dispositivo de transmissão Espelhar transmissão CloudStream Wiki @@ -651,30 +649,22 @@ O código expira em %1$dm %2$ds hide_player_control_names_key Reproduzir do começo - Atenção + Reprovou alguns testes Excluir plugin - Não há downloads no momento. + Você não baixou nada :/ Ocultar os nomes dos controles do player - Abrir vídeo local + Abrir arquivo de vídeo Data de lançamento (do novo ao antigo) Data de lançamento (do antigo para o novo) Selecionar itens para excluir - Excluir arquivos + Apagar arquivos Disponível para assistir offline Selecionar todos - Tem certeza de que deseja excluir permanentemente os seguintes itens? -\n -\n%s - Tem certeza de que deseja excluir permanentemente os seguintes episódios em %1$s? -\n -\n%2$s - Você também excluirá permanentemente todos os episódios da seguinte série: -\n -\n%s - Tem certeza de que deseja excluir permanentemente todos os episódios da seguinte série? -\n -\n%s - Excluir (%1$d | %2$s) + Tem certeza que deseja apagar permanentemente os seguintes itens?\n\n%s + Tem certeza que deseja apagar permanentemente os seguintes episódios em %1$s?\n\n%2$s + Você também apagará permanentemente todos os episódios da seguinte série:\n\n%s + Tem certeza que deseja apagar permanentemente todos os episódios da seguinte série?\n\n%s + Apagar %1$d (%2$s) Desmarcar todos Ativar visualização de miniatura na barra de busca Visualização da barra de busca @@ -686,9 +676,9 @@ Local da pasta de backup Personalizado Tamanho da borda - Este vídeo é um Torrent, o que significa que sua atividade de vídeo pode ser rastreada.\nCertifique-se de que você entendeu o que é Torrenting antes de continuar. + Este vídeo é um Torrent, isso significa que sua atividade de vídeo pode ser rastreada.\nTenha certeza de entender o que é Torrent antes de continuar. Erro de codificação - Erro não suportado + Erro, formato não suportado Carregar primeiro disponível Áudio Podcast @@ -712,7 +702,7 @@ Atualizar plugins manualmente Notificações de reprodução Notificação do player para controlar a reprodução em segundo plano - O reconhecimento de fala não está disponível + Reconhecimento de fala indisponível Comece a falar… Incorporada Online @@ -724,7 +714,7 @@ %1$dh %2$dm %3$ds %1$dm %2$ds %1$d s - Rótulo de Classificação + Indicação da classificação Perguntar sempre Quantos itens diferentes podem ser baixados em paralelo Downloads em paralelo @@ -739,4 +729,5 @@ Alternar Velocidade do Pressionamento Longo Segure para duplicar a velocidade Sem conta - \ No newline at end of file + Espelhar vídeo" + diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index b2ff4183145..c9a16a85861 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -766,4 +766,11 @@ URL bulunamadı Geçersiz Bağlantı veya Görsel Görsel Başarıyla Güncellendi - \ No newline at end of file + Ekranda oynat" + Bu bölümü izlenmiş olarak işaretle + Bu bölümü izlenmemiş olarak işaretle + Yeniden yükle + Sağlayıcıyı yeniden yükle + Ad + Çözünürlük ve ad + diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index 222c60e5f66..0c324f0aeb7 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -698,4 +698,7 @@ Вилучити переглянуті до цього епізоду Перезавантажено Постачальник послуг поповнення рахунку - \ No newline at end of file + Грати в дзеркало" + Ім\'я + Роздільна здатність та назва + diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index 0bc141d24a9..67f31a41ff3 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -735,4 +735,8 @@ Không tìm thấy url URL hoặc hình không hợp lệ Tải hình lên thành công - \ No newline at end of file + Đánh dấu là đã xem đến tập này + Xóa những tập đã xem đến tập này + Đã tải lại + Tải lại nguồn phát + diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 1f1c3645fe4..b5c9a1ecf0f 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -770,4 +770,7 @@ 去除这一集和之前集数的已观看状态 已重新加载 重新加载视频源 - \ No newline at end of file + 播放镜像" + 名称 + 分辨率和名称 + From 053692b6310934dcd4e62112e348eaa585c5952f Mon Sep 17 00:00:00 2001 From: Red-To-Tel Date: Fri, 10 Oct 2025 19:38:45 +0200 Subject: [PATCH 127/618] Update InAppUpdater.kt to fix white text on white background issue (#1967) --- .../main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 8bce8f63964..12befafe02e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -288,7 +288,7 @@ class InAppUpdater { ) } - val builder: AlertDialog.Builder = AlertDialog.Builder(this) + val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) builder.setTitle( getString(R.string.new_update_format).format( currentVersion?.versionName, From 0b73841e095223403351a6f8b14bc4f06230da17 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 10 Oct 2025 19:39:35 +0200 Subject: [PATCH 128/618] Fix: AlertDialogCustom inheritance fix --- app/src/main/res/values/styles.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d3981e073cd..b1d76b39e5a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -531,7 +531,7 @@ ?attr/colorPrimary - + + + + + + + + + + + + + + + + + From a95d8ddc781f4e7d99c18d9f05b1e9e227849896 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:53:55 -0700 Subject: [PATCH 328/618] Remove unnecessary overrideLibrary for torrServer (#2235) --- app/src/main/AndroidManifest.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f0ad909f88c..2a170932007 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,6 @@ - - From 3be396216f659b30823b0d977ffb884a3f5666e9 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:04:51 -0700 Subject: [PATCH 329/618] Remove acra and replace AcraApplication with CloudStreamApp (#2207) --- app/build.gradle.kts | 4 - app/src/main/AndroidManifest.xml | 2 +- .../lagradost/cloudstream3/AcraApplication.kt | 306 ++++-------------- .../lagradost/cloudstream3/CloudStreamApp.kt | 178 ++++++++++ .../lagradost/cloudstream3/CommonActivity.kt | 4 +- .../lagradost/cloudstream3/MainActivity.kt | 6 +- .../cloudstream3/actions/OpenInAppAction.kt | 4 +- .../cloudstream3/actions/temp/VlcPackage.kt | 2 +- .../actions/temp/fcast/FcastAction.kt | 2 +- .../cloudstream3/plugins/PluginManager.kt | 6 +- .../cloudstream3/plugins/RepositoryManager.kt | 6 +- .../cloudstream3/plugins/VotingApi.kt | 6 +- .../syncproviders/AccountManager.kt | 4 +- .../cloudstream3/syncproviders/AuthAPI.kt | 6 +- .../cloudstream3/syncproviders/AuthRepo.kt | 2 +- .../syncproviders/providers/AniListApi.kt | 4 +- .../syncproviders/providers/MALApi.kt | 4 +- .../syncproviders/providers/SimklApi.kt | 12 +- .../cloudstream3/ui/account/AccountHelper.kt | 2 +- .../ui/account/AccountViewModel.kt | 4 +- .../ui/download/DownloadButtonSetup.kt | 4 +- .../ui/download/button/PieFetchButton.kt | 2 +- .../ui/home/HomeParentItemAdapterPreview.kt | 2 +- .../cloudstream3/ui/home/HomeViewModel.kt | 4 +- .../ui/library/LibraryFragment.kt | 6 +- .../ui/library/LibraryViewModel.kt | 4 +- .../cloudstream3/ui/player/CS3IPlayer.kt | 4 +- .../ui/player/DownloadFileGenerator.kt | 2 +- .../cloudstream3/ui/player/GeneratorPlayer.kt | 6 +- .../ui/player/PreviewGenerator.kt | 4 +- .../source_priority/QualityDataHelper.kt | 6 +- .../ui/result/ResultViewModel2.kt | 6 +- .../cloudstream3/ui/search/SearchFragment.kt | 4 +- .../cloudstream3/ui/search/SearchViewModel.kt | 6 +- .../ui/settings/SettingsAccount.kt | 2 +- .../ui/settings/SettingsGeneral.kt | 10 +- .../ui/settings/SettingsProviders.kt | 2 +- .../cloudstream3/ui/settings/SettingsUI.kt | 2 +- .../ui/settings/SettingsUpdates.kt | 8 +- .../extensions/ExtensionsViewModel.kt | 2 +- .../ui/settings/extensions/PluginAdapter.kt | 2 +- .../extensions/PluginDetailsFragment.kt | 2 +- .../ui/settings/utils/DirectoryPicker.kt | 4 +- .../ui/setup/SetupFragmentLanguage.kt | 2 +- .../ui/setup/SetupFragmentLayout.kt | 2 +- .../subtitles/ChromecastSubtitlesFragment.kt | 2 +- .../ui/subtitles/SubtitlesFragment.kt | 4 +- .../cloudstream3/utils/AppContextUtils.kt | 2 +- .../cloudstream3/utils/BackupUtils.kt | 2 +- .../lagradost/cloudstream3/utils/DataStore.kt | 6 +- .../cloudstream3/utils/DataStoreHelper.kt | 19 +- .../utils/DownloadFileWorkManager.kt | 2 +- .../cloudstream3/utils/PackageInstaller.kt | 2 +- .../lagradost/cloudstream3/utils/UIHelper.kt | 2 +- .../utils/VideoDownloadManager.kt | 4 +- .../drawable/ic_baseline_bug_report_24.xml | 5 - .../main/res/layout/fragment_setup_layout.xml | 41 --- app/src/main/res/values-b+af/strings.xml | 1 - app/src/main/res/values-b+am/strings.xml | 1 - app/src/main/res/values-b+apc/strings.xml | 5 - app/src/main/res/values-b+ar/strings.xml | 5 - app/src/main/res/values-b+ars/strings.xml | 4 - app/src/main/res/values-b+as/strings.xml | 5 - app/src/main/res/values-b+az/strings.xml | 2 - app/src/main/res/values-b+bg/strings.xml | 5 - app/src/main/res/values-b+bn/strings.xml | 4 - app/src/main/res/values-b+ckb/strings.xml | 1 - app/src/main/res/values-b+cs/strings.xml | 5 - app/src/main/res/values-b+de/strings.xml | 5 - app/src/main/res/values-b+el/strings.xml | 5 - app/src/main/res/values-b+es/strings.xml | 5 - app/src/main/res/values-b+fa/strings.xml | 4 - app/src/main/res/values-b+fil/strings.xml | 3 - app/src/main/res/values-b+fr/strings.xml | 5 - app/src/main/res/values-b+gl/strings.xml | 4 - app/src/main/res/values-b+hi/strings.xml | 4 - app/src/main/res/values-b+hr/strings.xml | 5 - app/src/main/res/values-b+hu/strings.xml | 5 - app/src/main/res/values-b+in/strings.xml | 5 - app/src/main/res/values-b+it/strings.xml | 5 - app/src/main/res/values-b+iw/strings.xml | 5 - app/src/main/res/values-b+ja/strings.xml | 5 - app/src/main/res/values-b+kn/strings.xml | 1 - app/src/main/res/values-b+ko/strings.xml | 5 - app/src/main/res/values-b+lt/strings.xml | 1 - app/src/main/res/values-b+lv/strings.xml | 5 - app/src/main/res/values-b+mk/strings.xml | 5 - app/src/main/res/values-b+ml/strings.xml | 4 - app/src/main/res/values-b+ms/strings.xml | 5 - app/src/main/res/values-b+mt/strings.xml | 1 - app/src/main/res/values-b+my/strings.xml | 5 - app/src/main/res/values-b+ne/strings.xml | 1 - app/src/main/res/values-b+nl/strings.xml | 5 - app/src/main/res/values-b+nn/strings.xml | 2 - app/src/main/res/values-b+no/strings.xml | 5 - app/src/main/res/values-b+pl/strings.xml | 5 - app/src/main/res/values-b+pt+BR/strings.xml | 5 - app/src/main/res/values-b+pt/strings.xml | 5 - app/src/main/res/values-b+qt/strings.xml | 5 - app/src/main/res/values-b+ro/strings.xml | 5 - app/src/main/res/values-b+ru/strings.xml | 5 - app/src/main/res/values-b+sk/strings.xml | 5 - app/src/main/res/values-b+so/strings.xml | 5 - app/src/main/res/values-b+sv/strings.xml | 5 - app/src/main/res/values-b+ta/strings.xml | 5 - app/src/main/res/values-b+tl/strings.xml | 4 - app/src/main/res/values-b+tr/strings.xml | 5 - app/src/main/res/values-b+uk/strings.xml | 5 - app/src/main/res/values-b+ur/strings.xml | 5 - app/src/main/res/values-b+vi/strings.xml | 5 - app/src/main/res/values-b+zh+TW/strings.xml | 5 - app/src/main/res/values-b+zh/strings.xml | 5 - app/src/main/res/values-ca/strings.xml | 2 - app/src/main/res/values/strings.xml | 7 - app/src/main/res/xml/settings_updates.xml | 7 - gradle/libs.versions.toml | 3 - 116 files changed, 357 insertions(+), 647 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt delete mode 100644 app/src/main/res/drawable/ic_baseline_bug_report_24.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 19852437444..1a98ac2f36a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -199,10 +199,6 @@ dependencies { // FFmpeg Decoding implementation(libs.bundles.nextlibMedia3) - // Crash Reports (AcraApplication.kt) - implementation(libs.acra.core) - implementation(libs.acra.toast) - // UI Stuff implementation(libs.shimmer) // Shimmering Effect (Loading Skeleton) implementation(libs.palette.ktx) // Palette for Images -> Colors diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2a170932007..9e1bc9ac978 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,7 +33,7 @@ Unit)) : - Thread.UncaughtExceptionHandler { - override fun uncaughtException(thread: Thread, error: Throwable) { - ACRA.errorReporter.handleException(error) - try { - val threadId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) { - thread.threadId() - } else { - @Suppress("DEPRECATION") - thread.id - } - - PrintStream(errorFile).use { ps -> - ps.println("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}") - ps.println("Fatal exception on thread ${thread.name} (${threadId})") - error.printStackTrace(ps) - } - } catch (ignored: FileNotFoundException) { - } - try { - onError.invoke() - } catch (ignored: Exception) { - } - exitProcess(1) - } +/** + * Deprecated alias for CloudStreamApp for backwards compatibility with plugins. + * Use CloudStreamApp instead. + */ +// Deprecate after next stable +/* +@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp"), + level = DeprecationLevel.WARNING +)*/ +class AcraApplication { + companion object { + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.context"), + level = DeprecationLevel.WARNING + )*/ + val context get() = CloudStreamApp.context + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(path, value)"), + level = DeprecationLevel.WARNING + )*/ + fun setKey(path: String, value: T) = + CloudStreamApp.setKey(path, value) + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(folder, path, value)"), + level = DeprecationLevel.WARNING + )*/ + fun setKey(folder: String, path: String, value: T) = + CloudStreamApp.setKey(folder, path, value) + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(path, defVal)"), + level = DeprecationLevel.WARNING + )*/ + inline fun getKey(path: String, defVal: T?): T? = + CloudStreamApp.getKey(path, defVal) + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(path)"), + level = DeprecationLevel.WARNING + )*/ + inline fun getKey(path: String): T? = + CloudStreamApp.getKey(path) + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(folder, path)"), + level = DeprecationLevel.WARNING + )*/ + inline fun getKey(folder: String, path: String): T? = + CloudStreamApp.getKey(folder, path) + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(folder, path, defVal)"), + level = DeprecationLevel.WARNING + )*/ + inline fun getKey(folder: String, path: String, defVal: T?): T? = + CloudStreamApp.getKey(folder, path, defVal) + } } - -class AcraApplication : Application(), SingletonImageLoader.Factory { - - override fun onCreate() { - super.onCreate() - // if we want to initialise coil at earliest - // (maybe when loading an image or gif using in splash screen activity) - //ImageLoader.buildImageLoader(applicationContext) - - ExceptionHandler(filesDir.resolve("last_error")) { - val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName) - startActivity(Intent.makeRestartActivityTask(intent!!.component)) - }.also { - exceptionHandler = it - Thread.setDefaultUncaughtExceptionHandler(it) - } - } - - override fun attachBaseContext(base: Context?) { - super.attachBaseContext(base) - context = base - - initAcra { - //core configuration: - buildConfigClass = BuildConfig::class.java - reportFormat = StringFormat.JSON - - reportContent = listOf( - ReportField.BUILD_CONFIG, ReportField.USER_CRASH_DATE, - ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL, - ReportField.STACK_TRACE, - ) - - // removed this due to bug when starting the app, moved it to when it actually crashes - //each plugin you chose above can be configured in a block like this: - /*toast { - text = getString(R.string.acra_report_toast) - //opening this block automatically enables the plugin. - }*/ - } - } - - override fun newImageLoader(context: PlatformContext): coil3.ImageLoader { - // Coil Module will be initialized & setSafe globally when first loadImage() is invoked - return ImageLoader.buildImageLoader(applicationContext) - } - - companion object { - var exceptionHandler: ExceptionHandler? = null - - /** Use to get activity from Context */ - tailrec fun Context.getActivity(): Activity? { - return when (this) { - is Activity -> this - is ContextWrapper -> baseContext.getActivity() - else -> null - } - } - - private var _context: WeakReference? = null - var context - get() = _context?.get() - private set(value) { - _context = WeakReference(value) - setContext(WeakReference(value)) - } - - fun getKeyClass(path: String, valueType: Class): T? { - return context?.getKey(path, valueType) - } - - fun setKeyClass(path: String, value: T) { - context?.setKey(path, value) - } - - fun removeKeys(folder: String): Int? { - return context?.removeKeys(folder) - } - - fun setKey(path: String, value: T) { - context?.setKey(path, value) - } - - fun setKey(folder: String, path: String, value: T) { - context?.setKey(folder, path, value) - } - - inline fun getKey(path: String, defVal: T?): T? { - return context?.getKey(path, defVal) - } - - inline fun getKey(path: String): T? { - return context?.getKey(path) - } - - inline fun getKey(folder: String, path: String): T? { - return context?.getKey(folder, path) - } - - inline fun getKey(folder: String, path: String, defVal: T?): T? { - return context?.getKey(folder, path, defVal) - } - - fun getKeys(folder: String): List? { - return context?.getKeys(folder) - } - - fun removeKey(folder: String, path: String) { - context?.removeKey(folder, path) - } - - fun removeKey(path: String) { - context?.removeKey(path) - } - - /** - * If fallbackWebview is true and a fragment is supplied then it will open a webview with the url if the browser fails. - * */ - fun openBrowser(url: String, fallbackWebview: Boolean = false, fragment: Fragment? = null) { - context?.openBrowser(url, fallbackWebview, fragment) - } - - /** Will fallback to webview if in TV layout */ - fun openBrowser(url: String, activity: FragmentActivity?) { - openBrowser( - url, - isLayout(TV or EMULATOR), - activity?.supportFragmentManager?.fragments?.lastOrNull() - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt new file mode 100644 index 00000000000..6421f38c21e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt @@ -0,0 +1,178 @@ +package com.lagradost.cloudstream3 + +import android.app.Activity +import android.app.Application +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.os.Build +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import coil3.ImageLoader +import coil3.PlatformContext +import coil3.SingletonImageLoader +import com.lagradost.api.setContext +import com.lagradost.cloudstream3.mvvm.safe +import com.lagradost.cloudstream3.mvvm.safeAsync +import com.lagradost.cloudstream3.plugins.PluginManager +import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR +import com.lagradost.cloudstream3.ui.settings.Globals.TV +import com.lagradost.cloudstream3.ui.settings.Globals.isLayout +import com.lagradost.cloudstream3.utils.AppContextUtils.openBrowser +import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread +import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.getKeys +import com.lagradost.cloudstream3.utils.DataStore.removeKey +import com.lagradost.cloudstream3.utils.DataStore.removeKeys +import com.lagradost.cloudstream3.utils.DataStore.setKey +import com.lagradost.cloudstream3.utils.ImageLoader.buildImageLoader +import kotlinx.coroutines.runBlocking +import java.io.File +import java.io.FileNotFoundException +import java.io.PrintStream +import java.lang.ref.WeakReference +import java.util.Locale +import kotlin.concurrent.thread +import kotlin.system.exitProcess + +class ExceptionHandler( + val errorFile: File, + val onError: (() -> Unit) +) : Thread.UncaughtExceptionHandler { + + override fun uncaughtException(thread: Thread, error: Throwable) { + try { + val threadId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) { + thread.threadId() + } else { + @Suppress("DEPRECATION") + thread.id + } + + PrintStream(errorFile).use { ps -> + ps.println("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}") + ps.println("Fatal exception on thread ${thread.name} ($threadId)") + error.printStackTrace(ps) + } + } catch (_: FileNotFoundException) { + } + try { + onError() + } catch (_: Exception) { + } + exitProcess(1) + } +} + +@Prerelease +class CloudStreamApp : Application(), SingletonImageLoader.Factory { + + override fun onCreate() { + super.onCreate() + // If we want to initialize Coil as early as possible, maybe when + // loading an image or GIF in a splash screen activity. + // buildImageLoader(applicationContext) + + ExceptionHandler(filesDir.resolve("last_error")) { + val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName) + startActivity(Intent.makeRestartActivityTask(intent!!.component)) + }.also { + exceptionHandler = it + Thread.setDefaultUncaughtExceptionHandler(it) + } + } + + override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + context = base + } + + override fun newImageLoader(context: PlatformContext): ImageLoader { + // Coil module will be initialized globally when first loadImage() is invoked. + return buildImageLoader(applicationContext) + } + + companion object { + var exceptionHandler: ExceptionHandler? = null + + /** Use to get Activity from Context. */ + tailrec fun Context.getActivity(): Activity? { + return when (this) { + is Activity -> this + is ContextWrapper -> baseContext.getActivity() + else -> null + } + } + + private var _context: WeakReference? = null + var context + get() = _context?.get() + private set(value) { + _context = WeakReference(value) + setContext(WeakReference(value)) + } + + fun getKeyClass(path: String, valueType: Class): T? { + return context?.getKey(path, valueType) + } + + fun setKeyClass(path: String, value: T) { + context?.setKey(path, value) + } + + fun removeKeys(folder: String): Int? { + return context?.removeKeys(folder) + } + + fun setKey(path: String, value: T) { + context?.setKey(path, value) + } + + fun setKey(folder: String, path: String, value: T) { + context?.setKey(folder, path, value) + } + + inline fun getKey(path: String, defVal: T?): T? { + return context?.getKey(path, defVal) + } + + inline fun getKey(path: String): T? { + return context?.getKey(path) + } + + inline fun getKey(folder: String, path: String): T? { + return context?.getKey(folder, path) + } + + inline fun getKey(folder: String, path: String, defVal: T?): T? { + return context?.getKey(folder, path, defVal) + } + + fun getKeys(folder: String): List? { + return context?.getKeys(folder) + } + + fun removeKey(folder: String, path: String) { + context?.removeKey(folder, path) + } + + fun removeKey(path: String) { + context?.removeKey(path) + } + + /** If fallbackWebView is true and a fragment is supplied then it will open a WebView with the URL if the browser fails. */ + fun openBrowser(url: String, fallbackWebView: Boolean = false, fragment: Fragment? = null) { + context?.openBrowser(url, fallbackWebView, fragment) + } + + /** Will fall back to WebView if in TV or emulator layout. */ + fun openBrowser(url: String, activity: FragmentActivity?) { + openBrowser( + url, + isLayout(TV or EMULATOR), + activity?.supportFragmentManager?.fragments?.lastOrNull() + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 1e47548690a..e4e7c69f4d8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -28,8 +28,8 @@ import androidx.preference.PreferenceManager import com.google.android.gms.cast.framework.CastSession import com.google.android.material.chip.ChipGroup import com.google.android.material.navigationrail.NavigationRailView -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey import com.lagradost.cloudstream3.actions.OpenInAppAction import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.databinding.ToastBinding diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index cd3fde7f9ce..42d9c1869ba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -65,9 +65,9 @@ import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.lagradost.cloudstream3.APIHolder.allProviders import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.initAll -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.loadThemes import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt index eb6b1f936d4..ac912cbeb41 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt @@ -6,8 +6,8 @@ import android.content.Context import android.content.Intent import androidx.core.content.FileProvider import androidx.core.net.toUri -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.result.LinkLoadingResult diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt index e1fc22d3c56..46b46a2c2fe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt @@ -6,7 +6,7 @@ import android.content.Intent import android.os.Build import androidx.core.net.toUri import com.lagradost.api.Log -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.actions.OpenInAppAction import com.lagradost.cloudstream3.actions.makeTempM3U8Intent import com.lagradost.cloudstream3.actions.updateDurationAndPosition diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt index e3916df01c6..1036a70557c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt @@ -1,7 +1,7 @@ package com.lagradost.cloudstream3.actions.temp.fcast import android.content.Context -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.actions.VideoClickAction diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 1cffa7c1bfb..1b5d2909c3f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -20,11 +20,11 @@ import androidx.fragment.app.FragmentActivity import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.APIHolder.removePluginMapping -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.AutoDownloadMode +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index f8f0ccd7f64..45ed65611e7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -2,9 +2,9 @@ package com.lagradost.cloudstream3.plugins import android.content.Context import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt index d1b702f4ce3..9301066449a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt @@ -2,9 +2,9 @@ package com.lagradost.cloudstream3.plugins import android.util.Log import android.widget.Toast -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import java.security.MessageDigest import com.lagradost.cloudstream3.app diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt index 7e796fbd047..93df0fd26fa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt @@ -1,7 +1,7 @@ package com.lagradost.cloudstream3.syncproviders -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.syncproviders.providers.Addic7ed import com.lagradost.cloudstream3.syncproviders.providers.AniListApi diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt index e6b155a05b4..0303e03c654 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt @@ -4,10 +4,10 @@ import android.util.Base64 import androidx.annotation.WorkerThread import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.unixTime -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.ActorData +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.openBrowser +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.LoadResponse diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthRepo.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthRepo.kt index 9444c63679b..4ae629ab944 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthRepo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthRepo.kt @@ -1,6 +1,6 @@ package com.lagradost.cloudstream3.syncproviders -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser +import com.lagradost.cloudstream3.CloudStreamApp.Companion.openBrowser import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.R diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index a4cd42848d8..7a46b411376 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -2,11 +2,11 @@ package com.lagradost.cloudstream3.syncproviders.providers import androidx.annotation.StringRes import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.Actor import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.ActorRole +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.NextAiring import com.lagradost.cloudstream3.R diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index e8c343519b5..ba0195be6b8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -2,8 +2,8 @@ package com.lagradost.cloudstream3.syncproviders.providers import androidx.annotation.StringRes import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.ShowStatus diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt index 9518f5a20d2..c4095e2d881 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt @@ -4,12 +4,12 @@ import androidx.annotation.StringRes import androidx.core.net.toUri import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.BuildConfig +import com.lagradost.cloudstream3.CloudStreamApp +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.LoadResponse.Companion.readIdFromString import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.Score @@ -96,7 +96,7 @@ class SimklApi : SyncAPI() { fun cleanOldCache() { getKeys(SIMKL_CACHE_KEY)?.forEach { - val isOld = AcraApplication.getKey>(it)?.isFresh() == false + val isOld = CloudStreamApp.getKey>(it)?.isFresh() == false if (isOld) { removeKey(it) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt index 05253f98722..1d6b41e5baf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt @@ -21,7 +21,7 @@ import coil3.ImageLoader import coil3.request.ImageRequest import coil3.request.allowHardware import com.google.android.material.bottomsheet.BottomSheetDialog -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt index af62a2b0826..96eaf52a773 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt @@ -4,8 +4,8 @@ import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKeys import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.ui.account.AccountHelper.showPinInputDialog import com.lagradost.cloudstream3.utils.DataStoreHelper diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt index 83e0d016788..e9855ef3a3c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt @@ -4,8 +4,8 @@ import android.content.DialogInterface import android.net.Uri import androidx.appcompat.app.AlertDialog import com.google.android.material.snackbar.Snackbar -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeys import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt index 29c2daa2cf4..3181a1bcd94 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt @@ -12,7 +12,7 @@ import androidx.annotation.MainThread import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.core.view.isVisible -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DELETE_FILE diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index 042b05f9877..e6b82e47382 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -20,7 +20,7 @@ import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup import com.google.android.material.navigation.NavigationBarItemView -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.LoadResponse diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index bb82ec1a300..b7a322a8482 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -7,8 +7,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.LoadResponse diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index c2c226051f5..c9be2ed5c91 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -24,9 +24,9 @@ import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.APIHolder.allProviders -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.openBrowser +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt index f7713e9b24b..38f7fcf9dcb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt @@ -4,8 +4,8 @@ import androidx.annotation.StringRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.Resource diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 8a1756e5280..b7712cd796f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -63,8 +63,8 @@ import androidx.media3.exoplayer.trackselection.TrackSelector import androidx.media3.ui.SubtitleView import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt index 7aac845a57c..4c27dbc9772 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt @@ -1,7 +1,7 @@ package com.lagradost.cloudstream3.ui.player import android.net.Uri -import com.lagradost.cloudstream3.AcraApplication.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index a0d1e65d449..48353736b47 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -45,10 +45,10 @@ import androidx.media3.ui.PlayerNotificationManager.MediaDescriptionAdapter import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.AcraApplication -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.CloudStreamApp +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.databinding.DialogOnlineSubtitlesBinding import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding @@ -931,7 +931,7 @@ class GeneratorPlayer : FullScreenPlayer() { safe { // It lies, it can be null if file manager quits. if (uri == null) return@safe - val ctx = context ?: AcraApplication.context ?: return@safe + val ctx = context ?: CloudStreamApp.context ?: return@safe // RW perms for the path ctx.contentResolver.takePersistableUriPermission( uri, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt index 30e8d99ad8f..2893bcc47fd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt @@ -9,7 +9,7 @@ import android.util.Log import androidx.annotation.WorkerThread import androidx.core.graphics.scale import androidx.preference.PreferenceManager -import com.lagradost.cloudstream3.AcraApplication +import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -65,7 +65,7 @@ interface IPreviewGenerator { companion object { fun new(): IPreviewGenerator { - val userDisabled = AcraApplication.context?.let { ctx -> + val userDisabled = CloudStreamApp.context?.let { ctx -> PreferenceManager.getDefaultSharedPreferences(ctx)?.getBoolean( ctx.getString(R.string.preview_seekbar_key), true ) == false diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt index 0922bdb5aa6..467efa68b9c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt @@ -1,9 +1,9 @@ package com.lagradost.cloudstream3.ui.player.source_priority import androidx.annotation.StringRes -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.debugAssert import com.lagradost.cloudstream3.utils.UiText diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 5ad8f4f9ae5..106c05a3c32 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -11,14 +11,14 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.actions.AlwaysAskAction import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTimeMS +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.showToast @@ -1368,7 +1368,7 @@ class ResultViewModel2 : ViewModel() { // TODO Add skip loading here loadLinks(result, isVisible = true, sourceTypes, isCasting = isCasting) { links -> // Could not find a better way to do this - //val context = AcraApplication.context + //val context = CloudStreamApp.context postPopup( text, links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 8f9793ab6e4..d1efe62054b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -26,10 +26,10 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.AnimeSearchResponse +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKeys import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.MainAPI diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index de51ba009e5..63fb8c10ed8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -5,9 +5,9 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.APIHolder.apis -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.amap diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index 0693d044207..53d29cdb8e0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -17,7 +17,7 @@ import androidx.fragment.app.FragmentActivity import androidx.preference.PreferenceManager import androidx.preference.SwitchPreference import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser +import com.lagradost.cloudstream3.CloudStreamApp.Companion.openBrowser import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.ErrorLoadingException diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index fe2db95ecbb..e89865fc44f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -10,9 +10,9 @@ import androidx.core.os.ConfigurationCompat import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.allProviders -import com.lagradost.cloudstream3.AcraApplication -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity @@ -154,7 +154,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { ) private val pathPicker = getChooseFolderLauncher { uri, path -> - val context = context ?: AcraApplication.context ?: return@getChooseFolderLauncher + val context = context ?: CloudStreamApp.context ?: return@getChooseFolderLauncher (path ?: uri.toString()).let { PreferenceManager.getDefaultSharedPreferences(context).edit() .putString(getString(R.string.download_path_key), uri.toString()) @@ -317,7 +317,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { true, {}) { settingsManager.edit().putInt(getString(R.string.dns_pref), prefValues[it]).apply() - (context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } + (context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt index ddad2070dc7..8bc3371ea6c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt @@ -97,7 +97,7 @@ class SettingsProviders : BasePreferenceFragmentCompat() { selectedList.map { it.toString() }.toMutableSet() ).apply() DataStoreHelper.currentHomePage = null - //(context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } + //(context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index 924e8bc1a28..a991f9297c2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -5,7 +5,7 @@ import android.os.Bundle import android.view.View import androidx.preference.PreferenceManager import androidx.preference.SeekBarPreference -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchQuality diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 168ffeceaaa..30cd00470ab 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -8,8 +8,8 @@ import androidx.appcompat.app.AlertDialog import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager -import com.lagradost.cloudstream3.AcraApplication import com.lagradost.cloudstream3.AutoDownloadMode +import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.app @@ -56,7 +56,7 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { } private val pathPicker = getChooseFolderLauncher { uri, path -> - val context = context ?: AcraApplication.context ?: return@getChooseFolderLauncher + val context = context ?: CloudStreamApp.context ?: return@getChooseFolderLauncher (path ?: uri.toString()).let { PreferenceManager.getDefaultSharedPreferences(context).edit() .putString(getString(R.string.backup_path_key), uri.toString()) @@ -90,7 +90,7 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { settingsManager.edit() .putInt(getString(R.string.automatic_backup_key), prefValues[index]).apply() BackupWorkManager.enqueuePeriodicWork( - context ?: AcraApplication.context, + context ?: CloudStreamApp.context, prefValues[index].toLong() ) } @@ -250,7 +250,7 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { {}) { num -> settingsManager.edit() .putInt(getString(R.string.auto_download_plugins_key), prefValues[num]).apply() - (context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } + (context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt index 6d5e2ce27bc..482251b7831 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.mvvm.debugAssert diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt index c57013c5177..47b0b3da316 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt @@ -10,7 +10,7 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.TvType diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt index 1fc10058bed..0dcbece6cc3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt @@ -5,7 +5,7 @@ import android.text.format.Formatter.formatFileSize import android.util.Log import android.view.View import androidx.core.view.isVisible -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser +import com.lagradost.cloudstream3.CloudStreamApp.Companion.openBrowser import com.lagradost.cloudstream3.databinding.FragmentPluginDetailsBinding import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.VotingApi.canVote diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt index 9e126b7a6a5..08a79b4b497 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt @@ -4,14 +4,14 @@ import android.content.Intent import android.net.Uri import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment -import com.lagradost.cloudstream3.AcraApplication +import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.safefile.SafeFile fun Fragment.getChooseFolderLauncher(dirSelected: (uri: Uri?, path: String?) -> Unit) = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> // It lies, it can be null if file manager quits. if (uri == null) return@registerForActivityResult - val context = context ?: AcraApplication.context ?: return@registerForActivityResult + val context = context ?: CloudStreamApp.context ?: return@registerForActivityResult // RW perms for the path val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt index 946f7eeae7f..5ff85c53b4c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt @@ -6,8 +6,8 @@ import android.widget.ArrayAdapter import androidx.core.content.ContextCompat import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.BuildConfig +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.databinding.FragmentSetupLanguageBinding import com.lagradost.cloudstream3.mvvm.safe diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt index 6c4dfc86308..11cc12066fc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt @@ -5,7 +5,7 @@ import android.widget.AbsListView import android.widget.ArrayAdapter import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentSetupLayoutBinding import com.lagradost.cloudstream3.mvvm.safe diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt index ca7a33d89c2..4f41b436d14 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt @@ -18,7 +18,7 @@ import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_NONE import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_OUTLINE import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_RAISED import com.jaredrummler.android.colorpicker.ColorPickerDialog -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent import com.lagradost.cloudstream3.CommonActivity.showToast diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt index 2653e50115e..9b0d3121231 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt @@ -26,8 +26,8 @@ import androidx.media3.ui.SubtitleView import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty import com.jaredrummler.android.colorpicker.ColorPickerDialog -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent import com.lagradost.cloudstream3.CommonActivity.showToast diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt index 087f09b6d87..8334833e462 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -55,8 +55,8 @@ import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.wrappers.Wrappers import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.APIHolder.apis -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.AllLanguagesName +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.DubStatus diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt index 3e22bc65e94..1b67fe90c31 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt @@ -12,7 +12,7 @@ import androidx.fragment.app.FragmentActivity import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.module.kotlin.readValue -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt index e33a8f5e6d8..20d33c11218 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt @@ -6,9 +6,9 @@ import androidx.preference.PreferenceManager import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.module.kotlin.kotlinModule -import com.lagradost.cloudstream3.AcraApplication.Companion.getKeyClass -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKeyClass +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeyClass +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKeyClass import com.lagradost.cloudstream3.mvvm.logError import kotlin.reflect.KClass import kotlin.reflect.KProperty 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 ddb3c2cbb0e..217dc2a5205 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -3,13 +3,14 @@ package com.lagradost.cloudstream3.utils import android.content.Context import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.unixTimeMS -import com.lagradost.cloudstream3.AcraApplication -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeyClass +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKeyClass import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.EpisodeResponse @@ -56,7 +57,7 @@ class UserPreferenceDelegate( private val klass: KClass = default::class private val realKey get() = "${DataStoreHelper.currentAccount}/$key" operator fun getValue(self: Any?, property: KProperty<*>) = - AcraApplication.getKeyClass(realKey, klass.java) ?: default + getKeyClass(realKey, klass.java) ?: default operator fun setValue( self: Any?, @@ -66,7 +67,7 @@ class UserPreferenceDelegate( if (t == null) { removeKey(realKey) } else { - AcraApplication.setKeyClass(realKey, t) + setKeyClass(realKey, t) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt index 4eeb4e5da43..0b9b81e4024 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt @@ -7,7 +7,7 @@ import android.os.Build.VERSION.SDK_INT import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo import androidx.work.WorkerParameters -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt index 4be0dd56c30..67851f629cc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt @@ -11,7 +11,7 @@ import android.content.pm.PackageInstaller import android.os.Build import android.util.Log import android.widget.Toast -import com.lagradost.cloudstream3.AcraApplication.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.services.PackageInstallerService diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index ebafd4d7565..e114abe2942 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -67,7 +67,7 @@ import com.google.android.material.appbar.AppBarLayout import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipDrawable import com.google.android.material.chip.ChipGroup -import com.lagradost.cloudstream3.AcraApplication.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 022faeed4fd..9748bd29673 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -32,9 +32,9 @@ import coil3.request.ImageRequest import coil3.request.SuccessResult import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.BuildConfig +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.IDownloadableMinimum import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R diff --git a/app/src/main/res/drawable/ic_baseline_bug_report_24.xml b/app/src/main/res/drawable/ic_baseline_bug_report_24.xml deleted file mode 100644 index dad38dca6ce..00000000000 --- a/app/src/main/res/drawable/ic_baseline_bug_report_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/layout/fragment_setup_layout.xml b/app/src/main/res/layout/fragment_setup_layout.xml index a742c27b5b7..18815ced353 100644 --- a/app/src/main/res/layout/fragment_setup_layout.xml +++ b/app/src/main/res/layout/fragment_setup_layout.xml @@ -6,47 +6,6 @@ android:layout_height="match_parent" android:orientation="vertical"> - - Voorskou Agtergrond Speel Fliek Hou in om terug te stel na standaard - Deaktiveer outomatiese foutrapportering %1$dh %2$dm Maak leeg Omtrek kleur diff --git a/app/src/main/res/values-b+am/strings.xml b/app/src/main/res/values-b+am/strings.xml index f1b82c0bdc2..34b5c34336e 100644 --- a/app/src/main/res/values-b+am/strings.xml +++ b/app/src/main/res/values-b+am/strings.xml @@ -109,5 +109,4 @@ ዓይነቶችን በመጠቀም ይፈልጉ ቅርጸ-ቁምፊዎችን በ%s ውስጥ በማስቀመጥ ያጫኑ hide_player_control_names_key - አውቶማቲክ የሳንካ ሪፖርት ማድረግን አሰናክል \ No newline at end of file diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index 28bfa7e140c..e5e1844350c 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -6,9 +6,7 @@ التنزيلات %1$sالحلقة %2$d محي الملف - سوري، الآپ تعطل. رح ينبعت تقرير عن المشكلة للمطورين %1$d %2$s - بس بعات البيانات وقت ما يتعطل الآپ شوف الخلفية مشّي الفيلم ضلّ كابس لترجع السَتِنگز كيف كانة أول ما نزلتو الآپ @@ -63,7 +61,6 @@ لغة الترجمة لون الخلفية بتسيّڤ تاريخ المشاهدة و لوين وصلت بال ڤيديو - م بتبعت بيانات نبّش… هيدا المصدر مش عاطي \"ميتا داتا\". إزا مش موجودة بال مصدر، م رح يمشي الڤيديو. مافي أجزاء @@ -281,7 +278,6 @@ إشارات المرجعية التنزيل بَلَش فتّو على الأكونت \"%s\" - وقِف الإعلان الأتوماتيكي عن المشاكل يللي بال آپ محل عنوان الپوستر الشكل %1$d ساعة %2$d ديقة @@ -462,7 +458,6 @@ الإفتتاح الجودة وال عنوان شيل الإشتراك - بَلِغ عن الأعطال جودة وصوت ستبدل /؟؟ diff --git a/app/src/main/res/values-b+ar/strings.xml b/app/src/main/res/values-b+ar/strings.xml index 84e1d632111..2e1a4cdba9a 100644 --- a/app/src/main/res/values-b+ar/strings.xml +++ b/app/src/main/res/values-b+ar/strings.xml @@ -63,7 +63,6 @@ تشغيل الملف متابعة التنزيل إيقاف التنزيل مؤقتًا - قم بتعطيل الإبلاغ عن الأخطاء تلقائيًا مزيد من المعلومات إخفاء تشغيل @@ -143,8 +142,6 @@ معلومات البحث المتقدم يعطيك نتائج البحث مفصولة عن طريق المزود - إرسال البيانات عند الأعطال فقط - لا ترسل أي بيانات عرض حلقات الفلر للأنمي عرض المقاطع الدعائية عرض ملصقات من كيتسو @@ -167,7 +164,6 @@ تم نسخ الرابط إلى الحافظة تشغيل الحلقة إعادة التعيين إلى القيمة الافتراضية - عذرا، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين موسم لا موسم حلقة @@ -404,7 +400,6 @@ السابق تخطي الإعداد قم بتغيير مظهر التطبيق ليناسب جهازك - إبلاغ الأعطال ماذا تريد ان تري تم الإضافات diff --git a/app/src/main/res/values-b+ars/strings.xml b/app/src/main/res/values-b+ars/strings.xml index d1efdbbc536..80d34855027 100644 --- a/app/src/main/res/values-b+ars/strings.xml +++ b/app/src/main/res/values-b+ars/strings.xml @@ -85,11 +85,9 @@ معلومات التحديثات والنسخ الاحتياطي يعطيك نتائج البحث مفصولة حسب المزود - يرسل فقط البيانات عن الأعطال عرض المقطورات عرض الملصقات من كيتسو حسابات - لا يرسل أي بيانات عرض حلقة حشو للأنمي إخفاء جودة الفيديو المحددة في نتائج البحث تحديثات البرنامج المساعد التلقائي @@ -132,7 +130,6 @@ لا يتمتع هذا المزود بدعم كرومكاست لم يتم العثور على أي روابط تشغيل الحلقة - عذرًا، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين %1$s%2$d%3$s لا يوجد موسم حلقة @@ -218,7 +215,6 @@ لون النافذة ارتفاع الترجمة حذف ملف - تعطيل الإبلاغ التلقائي عن الأخطاء بدأ التحديث انسخ بث diff --git a/app/src/main/res/values-b+as/strings.xml b/app/src/main/res/values-b+as/strings.xml index 26d47be69b1..3a3db9ab6cb 100644 --- a/app/src/main/res/values-b+as/strings.xml +++ b/app/src/main/res/values-b+as/strings.xml @@ -138,7 +138,6 @@ চাব ফাইল মচক ফাইল প্লে কৰক - স্বয়ংক্ৰিয় বাগ ৰিপ’ৰ্টিং নিষ্ক্ৰিয় কৰক ডাউনলোড পুনৰ আৰম্ভ কৰক ডাউনলোড ৰখা অধিক তথ্য @@ -212,8 +211,6 @@ ব্যাকআপ ফাইল ল\'ড কৰা হৈছে উন্নত সন্ধান আপোনাক প্ৰদানকাৰীৰে পৃথককৃত সন্ধান ফলাফল দিব - কেৱল ভূলৰ ক্ষেত্ৰত ডাটা প্ৰেৰণ কৰে - কোনো ডাটা প্ৰেৰণ নকৰে এনিমেৰ ফিলাৰ খণ্ড দেখুৱাওক ট্ৰেইলাৰ দেখুৱাওক কিৎসুৰ পৰা প\'ষ্টাৰ দেখুৱাওক @@ -234,7 +231,6 @@ এই প্ৰদানকাৰীৰ ক্ৰোমকাষ্ট সমৰ্থন নাই লিংক ক্লিপব\'ৰ্ডত কপিকৰ কৰা হ’ল এপিচ’ড প্লে কৰক - দুখিত, এপ্পটোৰ এক্সিডেণ্ট হৈছে। এজনামবাগ বাগ ৰিপ’ৰ্ট ডেভেলপাৰক পঠোৱা হ’ব চিজন ডিফল্ট মানলৈ পুনৰছেট কৰক %1$s %2$d%3$s @@ -435,7 +431,6 @@ অপ্রয়োজনীয়তাবোৰ সাবটাইটেলৰ পৰা আঁতৰাওক অতিৰিক্ত ট্ৰেইলাৰ - ক্ৰেছৰ ৰিপৰ্টিং https://example.com/example.mp4 পূৰ্বৰ ছেটআপ এৰি দিয়া diff --git a/app/src/main/res/values-b+az/strings.xml b/app/src/main/res/values-b+az/strings.xml index 17ac40a07fb..430cd459350 100644 --- a/app/src/main/res/values-b+az/strings.xml +++ b/app/src/main/res/values-b+az/strings.xml @@ -87,12 +87,10 @@ Kitabxana Hesablar və Təhlükəsizlik Təkmilləşdirilmiş Axtarış - Məlumat göndərmə Treylerləri göstər Kitsu afişalarını, posterlərini göstər Link mübadilə buferinə nüsxələndi Standart dəyərlərə sıfırla - Üzr istəyirik, tətbiqdə xəta baş verdi. Gizli xəta hesabatı tərtibatçılara göndəriləcək Sezon Dayandır Başlat diff --git a/app/src/main/res/values-b+bg/strings.xml b/app/src/main/res/values-b+bg/strings.xml index ce9d11a2b29..2bd90287f02 100644 --- a/app/src/main/res/values-b+bg/strings.xml +++ b/app/src/main/res/values-b+bg/strings.xml @@ -67,7 +67,6 @@ Възпроизвеждане на файл Възобновете изтеглянето Пауза на изтеглянето - Деактивирайте автоматичното докладване на грешки Повече информация Скрий Пусни @@ -148,8 +147,6 @@ Информация Подробно търсене Дава ви резултатите от търсенето, разделени по доставчик - Изпраща данни само за сривове - Не изпраща данни Показване заместващ епизод за аниме Показване на трейлъри Покажете плакати от Kitsu @@ -171,7 +168,6 @@ Връзката е копирана в клипборда Пусни епизода Възстановяване на стойността по подразбиране - За съжаление приложението се срина. Анонимен доклад за грешка ще бъде изпратен до разработчиците Сезон %1$s %2$d%3$s Без сезон @@ -390,7 +386,6 @@ Предишен Пропуснете настройката Променете външния вид на приложението, за да отговаря на вашето устройство - Докладване за сривове Какво искате да видите Край Разширения diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index f1bc8f82ac1..b219587708d 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -62,7 +62,6 @@ সাব ফাইল চালান ডাউনলোড থামান - আটো বাগ রিপোর্ট বন্ধ করুন আরো তথ্য বন্ধ করুন চালান @@ -120,7 +119,6 @@ কালো প্রান্ত অপসারণ করুন অনুসন্ধান করুন অ্যাকাউন্টসমূহ এবং নিরাপত্তা - কোনো উপাত্ত পাঠাবে না বিরতি দিতে মাঝে দুইবার চাপুন সিস্টেম এর উজ্জ্বলতা ব্যবহার করুন ট্রেইলার চালু করুন @@ -150,8 +148,6 @@ প্লেয়ারে এগিয়ে যাওয়ার পরিমাণ (সেকেন্ডে) সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন ফাইল ডিলিট - দুঃখিত, অ্যাপ্লিকেশন ক্র্যাশ হয়েছে। ডেভেলপারদের কাছে একটি বেনামী বাগ রিপোর্ট পাঠানো হবে - শুধুমাত্র ক্র্যাশ এর তথ্য পাঠায় মান ডিফল্ট এ রিসেট করুন ফুল রিলিজের পরিবর্তে শুধুমাত্র প্রি-রিলিজ আপডেটের জন্য অনুসন্ধান করুন স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন diff --git a/app/src/main/res/values-b+ckb/strings.xml b/app/src/main/res/values-b+ckb/strings.xml index 79941c8e365..c47af36a3d4 100644 --- a/app/src/main/res/values-b+ckb/strings.xml +++ b/app/src/main/res/values-b+ckb/strings.xml @@ -72,7 +72,6 @@ سڕینەوەی فایل دووبارە دەستپێکردنەوەی دابەزاندن وەستاندنی دابەزاندن - ڕاپۆرتکردنی هەڵە بە شێوەیەکی ئۆتۆماتیکی لەکاربخە زانیاری زیاتر شاردنەوە زانیاری diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index e848582d378..70b74007907 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -65,7 +65,6 @@ Přehrát soubor Pokračovat ve stahování Pozastavit stahování - Zakázat automatické nahlašování chyb Více informací Skrýt Přehrát @@ -144,8 +143,6 @@ Informace Pokročilé hledání Zobrazí vám výsledky hledání oddělené poskytovatelem - Odešle data pouze při pádech - Nebude odesílat žádná data Zobrazit výplňové epizody u anime Zobrazit aktualizace aplikace Při spuštění aplikace automaticky zkontrolovat nové aktualizace. @@ -163,7 +160,6 @@ Odkaz zkopírován do schránky Přehrát epizodu Obnovit na výchozí hodnoty - Omlouváme se, aplikace spadla. Vývojářům bude odesláno anonymní hlášení o pádu Sezóna Žádná sezóna Epizoda @@ -425,7 +421,6 @@ NovýNázevWebu Povolit NSFW u podporovaných rozšíření Poskytovatelé - Hlášení pádů Předchozí Změnit vzhled aplikace tak, aby vám vyhovoval Co chcete vidět diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index 8d85c844843..fb4066d0145 100644 --- a/app/src/main/res/values-b+de/strings.xml +++ b/app/src/main/res/values-b+de/strings.xml @@ -78,7 +78,6 @@ Datei abspielen Download fortsetzen Download pausieren - Automatische Fehlerberichtserstattung deaktivieren Mehr Infos Verstecken Abspielen @@ -155,8 +154,6 @@ Info Erweiterte Suche Liefert die Suchergebnisse getrennt nach Anbietern - Sendet Daten nur bei Abstürzen - Sendet keine Daten Füller-Episoden für Animes anzeigen Trailer anzeigen Vorschaubilder von Kitsu anzeigen @@ -177,7 +174,6 @@ Link in die Zwischenablage kopiert Episode abspielen Auf Standardwert zurücksetzen - Sorry, die Anwendung ist abgestürzt. Ein anonymer Fehlerbericht wird an die Entwickler gesendet Staffel Keine Staffel Episode @@ -385,7 +381,6 @@ Vorherige Einrichtung überspringen Aussehen der App passend zu dem des Geräts ändern - Absturzmeldung Was möchten Sie sehen Fertig Erweiterungen diff --git a/app/src/main/res/values-b+el/strings.xml b/app/src/main/res/values-b+el/strings.xml index 933d8b53c51..9c6eb86a20d 100644 --- a/app/src/main/res/values-b+el/strings.xml +++ b/app/src/main/res/values-b+el/strings.xml @@ -42,8 +42,6 @@ Αναπαραγωγή αρχείου Συνέχιση λήψης Παύση λήψης - Λυπόμαστε, η εφαρμογή κατέρρευσε. Μια ανώνυμη αναφορά σφαλμάτων θα σταλεί στους προγραμματιστές - Απενεργοποιήστε την αυτόματη αναφορά σφαλμάτων Εμφάνιση Logcat 🐈 Περαιτέρω πληροφορίες Απόκρυψη @@ -102,8 +100,6 @@ Πληροφορίες Προχωρημένη Αναζήτηση Δίνει τα αποτελέσματα αναζήτησης ταξινομημένα ανά πάροχο - Αποστέλλει δεδομένα μόνο για καταρρεύσεις - Δεν στέλνει δεδομένα Εμφάνιση ενημερώσεων Αυτόματη αναζήτηση νέων ενημερώσεων κατά την εκκίνηση της εφαρμογής. Ενημέρωση σε προ-εκδόσεις (beta) @@ -307,7 +303,6 @@ Προηγούμενο Παράλειψη διαμόρφωσης της εφαρμογής Αλλαγή της εμφάνισης της συσκευής για να ταιριάζει με την συσκευή σας - Αναφορά κατάρρευσης Τι θα θέλατε να δείτε Έγινε Extensions diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index b5cadb42bfd..220f88ea3c5 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -223,8 +223,6 @@ Información Búsqueda Avanzada Mostrar los resultados de la búsqueda por proveedor - Solo envíar los datos si la App se cierra / falla inesperadamente - No enviar datos Mostrar avances Mostrar pósters de Kitsu Actualizar a las versiones preliminares @@ -239,7 +237,6 @@ Enlaces no encontrados Enlaces copiados al portapapeles Reiniciar a valores predefinidos - Lo sentimos, la aplicación se bloqueó. Se enviará un informe de error anónimo a los desarrolladores Temporada %1$s %2$d%3$s Ninguna Temporada @@ -286,7 +283,6 @@ Mostrar actualizaciones de la aplicación Instalador de APK Algunos dispositivos no soportan el nuevo instalador de paquetes. Pruebe la opción antigua (legacy) si las actualizaciones no se instalan. - Desactivar reporte automático de bugs Sincronizar automáticamente el progreso de su episodio actual Actualización automática de plugins Características del reproductor @@ -467,7 +463,6 @@ Instalando actualización de la aplicación… No se pudo instalar la nueva versión de la aplicación App no encontrada - Reporte de fallos Plugin Borrado No puede cargar %s Descripción diff --git a/app/src/main/res/values-b+fa/strings.xml b/app/src/main/res/values-b+fa/strings.xml index 2e348017cf5..496373cddd4 100644 --- a/app/src/main/res/values-b+fa/strings.xml +++ b/app/src/main/res/values-b+fa/strings.xml @@ -55,7 +55,6 @@ زیرنویس‌ها حذف دانلود آغاز شد - غیرفعال کردن گذارش باگ خودکار پاک کردن به‌روزرسانی آغاز شد کپی @@ -308,8 +307,6 @@ به‌روزرسانی پیشرفت ساعت شروع کنید به صحبت کردن… جواب سرچ های شما با تامین کننده - فقط درصورت کرش اطلاعات فرستاده می شوند - هیچ اطلاعاتی (دیتایی) فرستاده نمی شود نشان دادن آپدیت های برنامه دوباره پروسه راه اندازی را انجام بده نسخه های پیشین را آپدیت کن @@ -317,7 +314,6 @@ برنامه سبک رمان از توسعه دهندگان یکسان برنامه انیمه از توسعه دهندگان یکسان این ارائه دهنده هیچ پشتیبانی از کروم کست ندارد - متأسفیم، برنامه کرش کرد. یک گزارش از این باگ بطور ناشناس به توسعه دهندگان فرستاده می‌شود با شکست مواجه شد اخطار امتیاز diff --git a/app/src/main/res/values-b+fil/strings.xml b/app/src/main/res/values-b+fil/strings.xml index dbea6c62ff7..7ea37085263 100644 --- a/app/src/main/res/values-b+fil/strings.xml +++ b/app/src/main/res/values-b+fil/strings.xml @@ -40,7 +40,6 @@ Available para mapanood offline Piliin Lahat Alisin sa pagkakapili ang Lahat - Huwag paganahin ang awtomatikong pag-uulat ng bug Maaaring kailanganin ng VPN para gumana ang provider na ito Ang provider na ito ay isang torrent, inirerekomendang gumamit ng VPN Ang metadata ay hindi ibinigay ng site, hindi gagana ang video kung wala ito sa site. @@ -51,8 +50,6 @@ Awtomatikong i-sync ang iyong kasalukuyang episode Nawawala ang mga pahintulot sa storage. Pakisubukang muli. Binibigyan ka ng mga resulta ng paghahanap na pinaghihiwalay ng provider - Nagpapadala lamang ng data sa mga pag-crash - Hindi nagpapadala ng data Itago ang napiling quality ng video sa mga resulta ng paghahanap Pumili ng mode upang i-filter ang pag-download ng mga plugin Awtomatikong i-install ang lahat ng hindi pa naka-install na plugin mula sa mga idinagdag na repository. diff --git a/app/src/main/res/values-b+fr/strings.xml b/app/src/main/res/values-b+fr/strings.xml index c8b971a5bba..f7b638f0b3c 100644 --- a/app/src/main/res/values-b+fr/strings.xml +++ b/app/src/main/res/values-b+fr/strings.xml @@ -47,7 +47,6 @@ Lire le fichier Reprendre le téléchargement Mettre en pause le téléchargement - Désactiver le rapport de bug automatique Plus d\'information Cacher Affiche principale @@ -69,7 +68,6 @@ Lien copié dans le presse-papier Lecture de l\'episode Réinitialiser aux valeurs par défault - Désolé, l\'application à crashé. Un rapport de bug anonyme va être envoyé aux développeurs Saison Pas de Saison Épisode @@ -282,8 +280,6 @@ Info Recherche avancée Vous donne les résultats de la recherche séparés par fournisseur - Envoi de données uniquement en cas d\'accident - N\'envoie aucune donnée Afficher les épisodes spéciaux pour les animés Montrer les bandes-annonces Montrer les affiches provenant de Kitsu @@ -435,7 +431,6 @@ Certains téléphones ne supporte pas le nouvel installateur d\'application. Essayez l\'option de l\'ancien installateur si les mises-à-jour ne s\'installe pas. Précédent Ignorer la configuration - Rapport de crash Nom de dépôt (optionnel) plugin Supprimer le repository diff --git a/app/src/main/res/values-b+gl/strings.xml b/app/src/main/res/values-b+gl/strings.xml index d5a88bcec91..263a2acc307 100644 --- a/app/src/main/res/values-b+gl/strings.xml +++ b/app/src/main/res/values-b+gl/strings.xml @@ -76,7 +76,6 @@ Reproducir Arquivo Continuar Descarga Pausar Descarga - Desactivar reporte automático de bugs Máis información Agochar Reproducir @@ -153,8 +152,6 @@ Subtítulos Sincronizar automáticamente o progreso do episodio actual Use o brillo do sistema no reprodutor da aplicación en lugar dunha superposición oscura - Só envía datos se a aplicacción falla inesperadamente - Non enviar datos Mostrar episodio de recheo para Anime Mostrar Trailers Mostrar pósters de Kitsu @@ -278,7 +275,6 @@ Ligazóns non atopadas Ligazón copiada ó portapapeis Reproducir capítulo - O sentimos, o aplicativo bloqueouse. Enviarase un informe de error anónimo aos desenvolvedores Temporada Capítulos diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index 3d2216816b6..453718af97d 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -40,7 +40,6 @@ फ़ाइल चलाएं डाउनलोड फिर शुरू करें डाउनलोड रोकें - स्वचालित बग रिपोर्टिंग अक्षम करें और जानकारी छिपाएं चलाएं @@ -72,8 +71,6 @@ खोजें जानकारी खोज नतीजों को प्रोवाइडरों के हिसाब से अलग-अलग आपको दिखाता है - केवल क्रैश पर जानकारी भेजी जाएगी - आपकी जानकारी नहीं भेजी जाएगी हर बार एप खुलने पर स्वचालित रूप से नए अपडेट खोजें। केवल पूर्ण रिलीज़ के बजाय प्रीरिलीज़ अपडेट खोजें उन्हीं डेवलपर्स द्वारा Light novel ऐप @@ -86,7 +83,6 @@ कोई लिंक नहीं मिले लिंक क्लिपबोर्ड पर कॉपी किया गया एपिसोड चलायें - क्षमा करें, एप्प क्रैश हो गया है । निर्माताओं को एक अनाम बग रिपोर्ट भेजी जाएगी फ़ाइल डिलीट करें डिलीट रद्द करें diff --git a/app/src/main/res/values-b+hr/strings.xml b/app/src/main/res/values-b+hr/strings.xml index f65765e7e93..b34a691f7a1 100644 --- a/app/src/main/res/values-b+hr/strings.xml +++ b/app/src/main/res/values-b+hr/strings.xml @@ -80,7 +80,6 @@ Pokreni datoteku Nastavi preuzimanje Pauziraj preuzimanje - Onemogući automatsko izvješćivanje o greškama Više informacija Sakrij Pokreni @@ -161,8 +160,6 @@ Informacije Napredna pretraga Daje rezultate pretrage odvojene prema pružatelju usluga - Šalje samo podatke o padovima aplikacije - Ne šalje podatke Prikaži dodatnu epizodu za anime Prikaži trailere Prikaži postere iz Kitsua @@ -184,7 +181,6 @@ Poveznica je kopirana u međuspremnik Pokreni epizodu Vrati na zadanu vrijednost - Nažalost se aplikacija srušila. Anonimno izvješće o grešci će se poslati programerima Sezona Nema sezone Epizoda @@ -405,7 +401,6 @@ Prethodno Preskoči postavljanje Promijeni izgled aplikacije kako bi odgovarao tvom uređaju - Izvještavanje o rušenju Što želiš vidjeti Gotovo Proširenja diff --git a/app/src/main/res/values-b+hu/strings.xml b/app/src/main/res/values-b+hu/strings.xml index 02a07a3f626..593f1dc1232 100644 --- a/app/src/main/res/values-b+hu/strings.xml +++ b/app/src/main/res/values-b+hu/strings.xml @@ -80,7 +80,6 @@ Fájl lejátszása Letöltés folytatása Letöltés szüneteltetése - Automatikus hibajelentés kikapcsolása Több információ Elrejtés Lejátszás @@ -188,7 +187,6 @@ Hiba a biztonsági mentés során %s Fiókok és Biztonság Szolgáltató szerint elkülönítve adja meg a keresési eredményeket - Nem küld adatokat Poszterek megjelenítése Kitsu-ról Kiválasztott videóminőségek elrejtése keresési eredményekbe Automatikus bővítményfrissítések @@ -206,7 +204,6 @@ A link vágólapra másolva Epizód lejátszása Alapértelmezett értékre visszaállítása - Az alkalmazás összeomlott. Egy névtelen hibabejelentés küldünk a fejlesztőknek Évad %1$s %2$d%3$s Nincs évad @@ -253,7 +250,6 @@ Frissítés elkezdődött Nem sikerült visszaállítani az adatokat a %s fájlból Tárolási engedélyek hiányoznak. Kérjük próbálja újra. - Csak összeomlásokról küld adatokat APK Telepítő Egyes telefonok nem támogatják az új csomag telepítőt. Ha a frissítések nem települnek, próbálja meg a régi opciót. Banán adva @@ -405,7 +401,6 @@ Betöltés fájlból HDR Az alkalmazás megjelenésének módosítása, hogy az megfeleljen az eszközödnek - Összeomlás jelentése Nyilvános lista Állapot Összefoglaló diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index 592653734e8..404b6ae6564 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -63,7 +63,6 @@ Putar Berkas Lanjutkan Unduh Jeda Unduh - Nonaktifkan pelaporan bug otomatis Lebih banyak info Sembunyikan Putar @@ -141,8 +140,6 @@ Info Pencarian Lanjutan Memberikan hasil pencarian yang dipisahkan berdasarkan penyedia - Hanya mengirim data saat ngecrash - Tidak mengirim data Tampilkan episode filler untuk anime Tampilkan update aplikasi Secara otomatis mencari update terbaru setelah aplikasi dibuka. @@ -160,7 +157,6 @@ Tautan disalin ke papan klip Putar Episode Ulang ke pengaturan default - Maaf, aplikasi ngecrash. Laporan bug anonim akan dikirim ke developer Season Tidak Ada Season Episode @@ -453,7 +449,6 @@ Selanjutnya Sebelumnya Ubah tampilan aplikasi - Laporkan Crash Selesai Ekstensi Hapus Repositori diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index ac24a39d280..0c59b494bc8 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -70,7 +70,6 @@ Riproduci file Riprendi download Ferma download - Disabilita segnalazione automatica di bug Più info Nascondi Riproduci @@ -151,8 +150,6 @@ Info Ricerca avanzata Dividi i risultati della ricerca per provider - Invia i dati solo in caso di crash - Non inviare alcun dato Mostra tag [filler] per anime Mostra trailer Mostra poster da Kitsu @@ -174,7 +171,6 @@ Link copiato negli appunti Riproduci episodio Ripristina il valore predefinito - Spiacente, l\'applicazione è andata in crash. Una segnalazione anonima di bug sarà inviata agli sviluppatori Stagione %1$s %2$d%3$s Nessuna stagione @@ -395,7 +391,6 @@ Precedente Salta configurazione Cambia l\'aspetto dell\'app per adattarla al proprio dispositivo - Segnala crash Cosa vuoi vedere Fatto Estensioni diff --git a/app/src/main/res/values-b+iw/strings.xml b/app/src/main/res/values-b+iw/strings.xml index 037286ce1f2..ed83fcb8da9 100644 --- a/app/src/main/res/values-b+iw/strings.xml +++ b/app/src/main/res/values-b+iw/strings.xml @@ -58,7 +58,6 @@ נגן קובץ המשך הורדה השהה את ההורדה - השבת את דיווח הבאגים האוטומטי נגן מידע סנן סימניות @@ -191,7 +190,6 @@ עדכוני תוספים אוטומטיים כמות בנינים שניתנו עונה - מצטערים, האפליקציה קרסה. דוח באג אנונימי יישלח למפתחים טורנטים NSFW שגיאת מעבד @@ -283,8 +281,6 @@ חשבונות ובטיחות לא ניתנו בנינים מהירות ניגון - שולח נתונים רק בקריסות - לא שולח נתונים הצג טריילרים הצג פוסטרים מKitsu הסתר את איכות הסרטון שנבחרה בתוצאות החיפוש @@ -413,7 +409,6 @@ צפה בסרטונים בשפות אלה קודם שנה את מראה האפליקציה כך שיתאים למכשירך - דיווח על קריסה סוים הרחבות הוסף מאגר diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index 107c144341b..12de7a8fc8b 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -213,7 +213,6 @@ リンクの読み込みエラー 137905 リンクがリロードされました - 自動バグ報告を無効にする プレーヤーの速度 字幕設定 ダブ @@ -238,7 +237,6 @@ hide_player_control_names_key ブックマークのフィルタ プロットが見つかりません - データを送信しない ダブルタップで一時停止 トレーラーを表示 GitHub プロキシ @@ -315,7 +313,6 @@ アプリ起動後に自動的に更新を探します。 プレリリースにアップデート 初期値にリセット - 残念ながらアプリがクラッシュしました。匿名のバグ報告が開発者に送信されます %1$d %2$s エピソードが見つかりません アプリのテーマ @@ -565,12 +562,10 @@ 凹んだ QRコード画像 前へ - クラッシュレポート 表示したいもの %1$d %2$s をダウンロードしました プロフィールをロック パスワード/PIN認証 - クラッシュ時のみデータを送信 検索結果で選択したビデオ品質を非表示 追加されたリポジトリから未インストールのすべてのプラグインを自動的にインストールします。 セットアッププロセスを再実行 diff --git a/app/src/main/res/values-b+kn/strings.xml b/app/src/main/res/values-b+kn/strings.xml index 8be5a737b65..fcfaa904526 100644 --- a/app/src/main/res/values-b+kn/strings.xml +++ b/app/src/main/res/values-b+kn/strings.xml @@ -26,7 +26,6 @@ ಇಂಟರ್ನಲ್ ಸ್ಟೋರೇಜ್ ಡಬ್ ಸಬ್ - ಸ್ವಯಂಚಾಲಿತ ದೋಷ ವರದಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ಹೈಡ್ ಪ್ಲೇ ಮಾಹಿತಿ diff --git a/app/src/main/res/values-b+ko/strings.xml b/app/src/main/res/values-b+ko/strings.xml index 9ac7e11856c..7ec22c99115 100644 --- a/app/src/main/res/values-b+ko/strings.xml +++ b/app/src/main/res/values-b+ko/strings.xml @@ -48,7 +48,6 @@ 파일 재생 계속 다운로드 다운로드 일시정지 - 자동 오류 보고 비활성화 상세 정보 닫기 재생 @@ -156,7 +155,6 @@ 클립보드에 링크 복사됨 에피소드 재생 기본값으로 재설정 - 죄송합니다, 애플리케이션이 충돌했습니다. 버그 보고서가 익명으로 개발자에게 전송됩니다 에피소드 %1$d-%2$d 진행중 @@ -329,14 +327,12 @@ Picture-in-picture 플레이어 크기 조정 버튼 다른 앱 위에 있는 미니어처 플레이어에서 재생을 계속합니다 - 충돌에 관한 데이터만 전송 검은색 테두리 제거 오른쪽 또는 왼쪽을 두 번 탭하여 앞뒤로 탐색하기 자막 로드된 백업 파일 정보 고급 검색 - 데이터를 보내지 않습니다 설정 프로세스 다시 실행 APK 인스톨러 Github @@ -424,7 +420,6 @@ 플레이어 HDR TC - 충돌 보고 완료 다운로드되지 않음: %d HQ diff --git a/app/src/main/res/values-b+lt/strings.xml b/app/src/main/res/values-b+lt/strings.xml index cc68d77eb88..6493e33e2e1 100644 --- a/app/src/main/res/values-b+lt/strings.xml +++ b/app/src/main/res/values-b+lt/strings.xml @@ -9,7 +9,6 @@ Sekantis atsitiktinai Peržiūros fonas Paleisti filmą - Išjungti automatini klaidų pateikimą Atstatyti į numatytą reikšmę Išvalyti Plakatas diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index cd981810e08..bff199dfbf0 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -56,7 +56,6 @@ Palaist failu Atsākt ielādi Pauzēt ielādi - Atslēgt automātisko kļūdu ziņošanu Vairāk informācijas Slēpt Atskaņot @@ -153,8 +152,6 @@ Informācija Advancēta meklēšana Dod tev meklēšanas rezultātus citus no devēja - Tikai sūtīt datus no kļudām - Nesutīt datus Radīt fillera epizodi priekš animē Radīt feel Radīt plakātu no kitsu @@ -389,7 +386,6 @@ Iepriekšējais Izlaist uzstādīšanu Mainiet lietotnes izskatu, lai tā atbilstu savai ierīcei - Avārijas ziņošana Ko tu vēlies redzēt Pabeigts Papildinājumi @@ -473,7 +469,6 @@ Github Nav subtitru Atskaņot epizodi - Piedodiet, bet aplikācijā bija kļūda. Anonīms kļūdas ziņojums tika aizsūtīts izstrādātājiem Iet Bezmaksas Ieslēgt elementus uz plakātiem diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index 95f6fa7ec59..1af176a1290 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -47,7 +47,6 @@ Пушти датотека Продолжи со преземање Паузирај со преземање - Оневозможи автоматско известување за грешки Повеќе информации Скриј Пушти @@ -101,8 +100,6 @@ Информации Напредно пребарување Ви ги дава резултатите од пребарувањето одделени по провајдер - Испраќај податоци само за падови на апликацијата - Не испраќа податоци Прикажи епизода за полнење за аниме Прикажи ажурирања на апликации Автоматски пребарувај нови ажурирања откако ќе ја стартуваш апликацијата. @@ -120,7 +117,6 @@ Линкот е копиран во таблата со исечоци Пушти ја епизодата Ресетирање на стандардните вредности - За жал, апликацијата падна. Ќе се испрати анонимен извештај за грешка до програмерите Сезона Нема сезона Епизода @@ -466,7 +462,6 @@ Претходно Прескокни го поставувањето Промени го изгледот на апликацијата за да одговара на твојот уред - Известување за пад Што сакате да видите Име на изворот (Опционално) Приклучокот е преземен diff --git a/app/src/main/res/values-b+ml/strings.xml b/app/src/main/res/values-b+ml/strings.xml index bf0fede72d1..c1077768137 100644 --- a/app/src/main/res/values-b+ml/strings.xml +++ b/app/src/main/res/values-b+ml/strings.xml @@ -45,7 +45,6 @@ ഫയൽ പ്ലേയ് ചെയ്യുക ഡൌൺലോഡ് തുടരുക ഡൌൺലോഡ് നിർത്തുക - ഓട്ടോമാറ്റിക് ബഗ് റിപ്പോർട്ടിംഗ് പ്രവർത്തനരഹിതമാക്കുക കൂടുതൽ വിവരം ഒളിക്കുക പ്ലേയ് @@ -94,8 +93,6 @@ വിവരം സ്ട്രോതസായി തിരിച്ച ഫലം തരുക - ക്രാഷാകുമ്പോൾ മാത്രം അയക്കുക - ടാറ്റ അയക്കാതിരിക്കുക അപ്ഡേറ്റുകൾ അറിയിക്കുക ആരംഭത്തിൽ അപ്ഡേറ്റുകൾ തിരയുക പരീക്ഷണാത്മക അപ്ഡേറ് @@ -111,7 +108,6 @@ ലിങ്കുകൾ ലഭ്യമല്ല ലിങ്ക് പകർത്തിയിരിക്കുന്നു എപ്പിസോഡ് പ്ലേയ് ചെയ്യുക - ആപ്പ് നിശ്ചലമായിരിക്കുന്നതിന് ക്ഷമിക്കണം സീസൺ സീസണില്ല എപ്പിസോഡ് diff --git a/app/src/main/res/values-b+ms/strings.xml b/app/src/main/res/values-b+ms/strings.xml index e93dc50151f..9038a51e7bb 100644 --- a/app/src/main/res/values-b+ms/strings.xml +++ b/app/src/main/res/values-b+ms/strings.xml @@ -66,7 +66,6 @@ Buang disalin! Kelajuan Pemain - Lumpuhkan pelaporan pepijat automatik Buang Mulakan episod seterusnya apabila episod semasa tamat Buka video tempatan @@ -163,7 +162,6 @@ Pemain Aplikasi akan dikemas kini ketika keluar Pemain dalaman - Tiada data dihantar Durasi Laman web Sinopsis @@ -416,7 +414,6 @@ HD Sebelum Tukar kelihatan aplikasi mengikut peranti - Laporkan ralat Tamat Tambahan Tambah repositori @@ -514,7 +511,6 @@ Pautan disalin ke papan klip Main episod Tetapkan semula ke nilai lalai - Maaf, permohonan itu terhempas. Laporan bug tanpa nama akan dihantar kepada pemaju Musim %1$s %2$d%3$s Tiada musim @@ -522,7 +518,6 @@ Sandarkan data Gagal pulihkan data dari fail %s Ralat sandaran %s - Hanya hantar data apabila mengalami kegagalan Episode diff --git a/app/src/main/res/values-b+mt/strings.xml b/app/src/main/res/values-b+mt/strings.xml index ee890fbbd05..bd605bb3782 100644 --- a/app/src/main/res/values-b+mt/strings.xml +++ b/app/src/main/res/values-b+mt/strings.xml @@ -115,7 +115,6 @@ Hassar il-fajl Kompli Nizzel Ieqaf Nizzel - Iddiżattiva r-rappurtar awtomatiku tal-bugs Iktar Informazzjoni Aħbi Iffiltra l-Bookmarks diff --git a/app/src/main/res/values-b+my/strings.xml b/app/src/main/res/values-b+my/strings.xml index 9d82dd47dbd..6a991f8856d 100644 --- a/app/src/main/res/values-b+my/strings.xml +++ b/app/src/main/res/values-b+my/strings.xml @@ -92,7 +92,6 @@ အပ်ဒိတ်များနှင့်အရန်သိမ်းဆည်းမှု နက်နက်ရှိုင်းရှိုင်းရှာခြင်း သင့်ကိုဝန်ဆောင်မှုပေးသူအလိုက်ရှာဖွေမှုရလဒ်များပေးမည် - ချို့ယွင်းမှုအကြီးစားဖြစ်မှသာဒေတာများပေးပို့ပါ anime များအတွက်ဖြည့်စွက်အပိုင်းကိုပြရန် ထွေလာများကိုပြရန် Kitsu မှ ပိုစတာများကိုပြရန် @@ -239,7 +238,6 @@ ဖိုင်ကို ဖွင့်ရန် ဒေါင်းလုဒ် ဆက်လုပ်ရန် ဒေါင်းလုဒ် ရပ်ရန် - အလိုအလျောက်အက်ပ်ချို့ယွင်းချက်ပေးပို့ခြင်းကိုပိတ်မည် ပိုမို၍ ပ့ံပိုးပေးသောဝန်ဆောင်မှုများအသုံးပြု၍ရှာရန် ဝုက်ရန် @@ -267,7 +265,6 @@ ရှာရန် အကောင့်များ အချက်အလက် - ဒေတာများမပို့ရန် ကြည့်ရှုပြီးသောအချိန်ပမာဏ Android TV ကဲ့သို့သော သိုလှောင်မှုနေရာနည်းပါးသော စက်ပစ္စည်းများတွင် အလွန်မြင့်မားစွာ သတ်မှတ်ပါက ပြဿနာများ ဖြစ်လာနိုင်သည်။ ISP ပိတ်ဆို့ခြင်းကို ကျော်လွှားရန်အတွက် အသုံးဝင်သည် @@ -287,7 +284,6 @@ ပေးခဲ့သောစာအရေအတွက် အက်ပ်ဘာသာစကား မူလအခြေအနေများကိုပြန်ထားပါ - စိတ်မကောင်းပါ။အက်ပ်ရပ်တန့်သွားပါတယ်။အမည်မဖော်ထားတဲ့တင်ပြချက်ကို အက်ပ်ရေးသားသူများထံ ပို့မှာဖြစ်ပါတယ် %1$s %2$d%3$s အတွဲ %1$d %2$s @@ -457,7 +453,6 @@ TC အရည်အသွေး အစီအစဥ်ချခြင်းကိုကျော်မည် - ချို့ယွင်းမှုသတင်းပေးပို့ခြင်း ဘာတွေကြည့်ချင်လဲ ပြီးပြီ အဆက်များ diff --git a/app/src/main/res/values-b+ne/strings.xml b/app/src/main/res/values-b+ne/strings.xml index bff19dd3a98..4eea78b9c7a 100644 --- a/app/src/main/res/values-b+ne/strings.xml +++ b/app/src/main/res/values-b+ne/strings.xml @@ -83,7 +83,6 @@ पुन: हेर्दै स्ट्रिम टोरेन्ट स्रोतहरू - स्वचालित बग रिपोर्टिङ असक्षम गर्नुहोस् लागू गर्नुहोस् साइट ले मेटाडाटा दिएको छैन,मेटाडाटा बिना भिडियो लोड नहुन सक्छ। प्रकरण %1$d प्रसङ्ग %2$d प्रशारण हुनेवाला छ diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index b10821a3529..2497c0eba00 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -71,7 +71,6 @@ Bestand afspelen Download hervatten Download pauzeren - Automatische bugrapportage uitschakelen Meer info Verberg Speel @@ -150,8 +149,6 @@ Info Geavanceerd zoeken Geeft u de zoekresultaten gescheiden door provider - Stuurt alleen gegevens bij crashes - Verstuurt geen gegevens Toon filler episode voor anime Toon trailers Toon posters van Kitsu @@ -171,7 +168,6 @@ Link gekopieerd naar klembord Aflevering afspelen Reset naar standaardwaarde - Sorry, de applicatie is gecrasht. Er wordt een anoniem bugrapport naar de ontwikkelaars gestuurd Seizoen Geen seizoen Aflevering @@ -392,7 +388,6 @@ Vorige Instelling overslaan Pas het uiterlijk van de app aan uw apparaat aan - Crashrapportage Wat wil je zien Klaar Markeer als bekeken diff --git a/app/src/main/res/values-b+nn/strings.xml b/app/src/main/res/values-b+nn/strings.xml index 5b5577c2e1d..6989a85da31 100644 --- a/app/src/main/res/values-b+nn/strings.xml +++ b/app/src/main/res/values-b+nn/strings.xml @@ -54,7 +54,6 @@ Slett fil Spel av fil Sett nedlasting på vent - Deaktiver automatisk feilrapportering Gøym Spel(e) av Informasjon @@ -103,7 +102,6 @@ Kopiert lenke til utklipptavle Spel av episode Tilbakestill til standardverdi - Beklager, programmet har krasjet. Ein anonymisert feilrapport vil bli sendt til utviklarane %1$s %2$d%3$s Ingen sesong Episode diff --git a/app/src/main/res/values-b+no/strings.xml b/app/src/main/res/values-b+no/strings.xml index ac13f57f996..8fa0d8ecab7 100644 --- a/app/src/main/res/values-b+no/strings.xml +++ b/app/src/main/res/values-b+no/strings.xml @@ -57,7 +57,6 @@ Spill av fil Fortsett nedlasting Stopp nedlastingen - Deaktiver automatisk feilrapportering Mer informasjon Gjemme seg Spille @@ -110,8 +109,6 @@ Informasjon Avansert søk Gir deg søkeresultatene atskilt etter leverandør - Sender bare rapportere om krasjer - Sender ingen rapportere Vis filler-episode til anime Vis appoppdateringer Søk automatisk etter nye oppdateringer etter at appen har startet. @@ -129,7 +126,6 @@ Linken er kopiert til utklippstavlen spille episode tilbakestill standardverdien - Beklager, programmet krasjet. En anonym feilrapport vil bli sendt til utviklerne Sesong Ingen sesong Episode @@ -346,7 +342,6 @@ passord123 Språkkode (nb_NO) Forrige - Krasjrapportering Utvidelser %1$d %2$s MittKuleBrukernavn diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index 3bff02c2b4f..9d5043d3a4f 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -61,7 +61,6 @@ Odtwórz plik Wznów pobieranie Wstrzymaj pobieranie - Wyłącz przekazywanie błędów Więcej informacji Ukryj Odtwórz @@ -141,8 +140,6 @@ Informacje Zaawansowane wyszukiwanie Szukaj z podziałem na źródła - Wysyłaj dane tylko przy awariach - Nie wysyłaj żadnych danych Pokaż fillery dla anime Pokaż zwiastuny Pokaż obrazki z Kitsu @@ -165,7 +162,6 @@ Skopiowano do schowka Odtwórz odcinek Zresetowano - Awaria aplikacji. Anonimowe zgłoszenie błędu zostanie wysłane programistom Sezon %1$s %2$d%3$s Brak sezonu @@ -368,7 +364,6 @@ Poprzedni Pomiń konfigurację Dostosuj wygląd aplikacji do urządzenia - Zgłaszanie błędów Co chcesz obejrzeć Gotowe Rozszerzenia diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index e5871672dcf..e82aa0b8c80 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -69,7 +69,6 @@ Reproduzir arquivo Continuar download Pausar download - Desative o relatório automático de erros Mais informações Ocultar Reproduzir @@ -148,8 +147,6 @@ Info Pesquisa avançada Mostrar resultados separados por fornecedor - Enviar apenas dados de falhas - Não enviar nenhum dado Mostrar episódios extras de animes Mostrar trailers Mostrar posters do Kitsu @@ -171,7 +168,6 @@ Link copiado para área de transferência Assistir episódio Restaurar para o padrão - Desculpe :/, o aplicativo travou. Um relatório de erro anônimo será enviado aos desenvolvedores Temporada Nenhuma temporada Episódio @@ -390,7 +386,6 @@ Anterior Saltar setup Change the look of the app to suit your device - Crash reporting What do you want to see Feito Extensões diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index f6ccf2d03ea..67706ed1292 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -68,7 +68,6 @@ Reproduzir Ficheiro Retomar Transferência Pausar Transferência - Desativar relatório automático de erros Mais info Esconder Reproduzir @@ -147,8 +146,6 @@ Info Procura Avançada Mostra resultados separados por fornecedor - Só envia dados sobre falhas - Não envia nenhum dado Mostrar episódios de enchimento para anime Mostrar trailers Mostrar posters do kitsu @@ -169,7 +166,6 @@ Link copiado para a área de transferência Reproduzir episódio Restaurar para o padrão - Desculpe, a aplicação falhou. Um relatório de erro anónimo será enviado para os desenvolvedores Temporada Nenhuma Temporada Episódio @@ -349,7 +345,6 @@ Anterior Saltar setup Change the look of the app to suit your device - Crash reporting What do you want to see Feito Extensões diff --git a/app/src/main/res/values-b+qt/strings.xml b/app/src/main/res/values-b+qt/strings.xml index 2ca5a5881f2..246134c66d9 100644 --- a/app/src/main/res/values-b+qt/strings.xml +++ b/app/src/main/res/values-b+qt/strings.xml @@ -38,7 +38,6 @@ ahooo ouuhhh ahhaauugghh - ooo-ahahoohahoooooo-ahahouuhhhoouuhaaahhu ahooo a aaaghhouuhhh oh ahhh ahhhahaaaaaahhhaaaghh @@ -91,8 +90,6 @@ aauugghh ah aah ouuhhhooo-ahah aaaghh aauugghh ahahooo ouuhhhahh ooh oouuhahoooahhaaahhu ohaooh oouuhooo-ahah - aaaaa ahhhahhohoouuhahoooaaaghh aahhhaaaaa - oouuhoooohh ahhooo-ahah haa oohaauugghhooh oh aaaagggoooogg uuuugg aak aah aaaahhh ooogg uuuuuukh aah ooh aaaghh ahhhahoooooo-ahah aaaghh @@ -109,7 +106,6 @@ ooh oouuh ahoooah ohaa oh ouuhhh ouuhhhoouuh ouuhhh haaahhh ooo-ahah haaoh haaooh - oooooahoohaaaghh oouuhoooohhaaaaaoha ohaaauugghh oohaaaaaahooo ooo-ahah aaaaaa ahooo ahooo oouuhoooohh oooohhaaaghh oouuh ooo-ahah aauuh ohaahooo @@ -407,7 +403,6 @@ uuuugg aaaaggg ug aah uug aahh ug uuuuhhh oohh aah memory, oohh og oooohhh ug aaaahhh oooogggh - ooohh aaaaaakag ooogg aaaaggg aaaahhhuuhh %1$d %2$s… uuuhh aaaagg diff --git a/app/src/main/res/values-b+ro/strings.xml b/app/src/main/res/values-b+ro/strings.xml index a49bcb6f972..4c16853196c 100644 --- a/app/src/main/res/values-b+ro/strings.xml +++ b/app/src/main/res/values-b+ro/strings.xml @@ -68,7 +68,6 @@ Redare fișier Continuați descărcarea Opriți descărcarea - Dezactivați raportarea automată a erorilor Mai multe informații Ascunde Începe @@ -147,8 +146,6 @@ Informații Căutare avansată Împărțiți rezultatele căutării în funcție de furnizor - Trimiteți date numai în cazul unui accident - Nu trimiteți niciun fel de date Afișează etichetele [filler] pentru anime Arată trailerul Arată afișele de la Kitsu @@ -168,7 +165,6 @@ Link copiat în clipboard Redare episod Restabilirea la valorile implicite - Ne pare rău, aplicația s-a blocat. Un raport de eroare anonim va fi trimis dezvoltatorilor Sezonul Nu există sezon Episodul @@ -436,7 +432,6 @@ Linkuri Funcții Autori - Raportarea accidentelor Adaugă depozit Biblioteca ta este goală :( \nConectați-vă într-un cont de bibliotecă sau adăugați emisiuni la biblioteca locală. diff --git a/app/src/main/res/values-b+ru/strings.xml b/app/src/main/res/values-b+ru/strings.xml index aa5e24ccc91..bf22e890179 100644 --- a/app/src/main/res/values-b+ru/strings.xml +++ b/app/src/main/res/values-b+ru/strings.xml @@ -117,7 +117,6 @@ Внутренняя память Продолжить Скачать Остановить скачивание - Отключить автоматическое информирование об ошибках Импортируйте шрифты поместив их в %s Продолжить смотреть Убрать @@ -165,7 +164,6 @@ Ссылок не найдено Ссылка скопирована в буфер обмена Восстановить по умолчанию - Извините, приложение прекратило работу. Анонимный отчёт об ошибке будет отправлен разработчикам Серия Серии С @@ -220,7 +218,6 @@ Дополнения Плеер Резервное копирование данных - Отправлять данные только при вылетах Использовано Двойное нажатие для паузы Коснитесь дважды правой или левой стороны для поиска вперед или назад @@ -453,7 +450,6 @@ Скачивание обновления приложения… Недопустимый URL Перезапустите приложение, чтобы увидеть изменения. - Отчеты ошибках Что вы хотите увидеть Смотрите видео на этих языках Скачано файл @@ -485,7 +481,6 @@ Отображать случайную кнопку в библиотеке и главной странице Случайная кнопка Устаревший - Не отправляет данные Перезагрузить ссылки Предпочтительные медиа Опущенные diff --git a/app/src/main/res/values-b+sk/strings.xml b/app/src/main/res/values-b+sk/strings.xml index b13cbcc1192..19be2268466 100644 --- a/app/src/main/res/values-b+sk/strings.xml +++ b/app/src/main/res/values-b+sk/strings.xml @@ -50,7 +50,6 @@ Načítavanie… Dokončené Plánujem pozerať - Zakázať automatické nahlasovanie chýb Viac informácií Záložky Prehrať film @@ -151,7 +150,6 @@ Dvojitým ťuknutím pretočiť Automaticky sťahovať doplnky Pripojte sa na Discord - Neodosiela žiadne dáta Odstrániť čierne okraje Automaticky vyhľadať nové aktualizácie po spustení aplikácie. Prehrať epizódu @@ -167,7 +165,6 @@ V prehrávači použiť systémový jas namiesto tmavého prekrytia Zobraziť upútavky Automaticky nainštalovať všetky ešte nenainštalované doplnky z pridaných repozitárov. - Odosiela dáta len pri pádoch Knižnica GitHub Hľadať @@ -180,7 +177,6 @@ Zobraziť aktualizácie aplikácie Aktualizácia na predbežné vydania Vyhľadať aktualizácie predbežných vydaní namiesto plných vydaní - Ospravedlňujeme sa, aplikácia spadla. Vývojárom bude odoslané anonymné hlásenie o páde Obnoviť predvolenú hodnotu Sezóna Synopsa @@ -435,7 +431,6 @@ Neplatné dáta Neplatná URL adresa Odstráňte uzavreté titulky od titulkov - Správy zlyhania Zobraziť odporúčania Otestujte všetky rozšírenia Nadchádzajúce o %s diff --git a/app/src/main/res/values-b+so/strings.xml b/app/src/main/res/values-b+so/strings.xml index 8b1e4cc1511..44a9b6d907e 100644 --- a/app/src/main/res/values-b+so/strings.xml +++ b/app/src/main/res/values-b+so/strings.xml @@ -67,7 +67,6 @@ Trj Way bilaabmatay soo dejintu Daalaco - Jooji cillad gudbinta iskeed ah Waa la joojiyey dejintan Way bilaabantay cuaboonaysiintu Soo raridda lifaaqyadu way fashilantay @@ -96,7 +95,6 @@ Gurmadka iyo cusbooneysiinta Waa la kaydiyet xogta gurmadka Raadinta waxay kuugu kala qaybinaysaa mid kasta iyo qaybiyihiisa - Ma jirto xog la dirayo Way fashilantay in xogta laga soo celiyo faylka gurmadka%s Moobillada qaar ayaa awoodin iney Rakibaha cusub een isticmaalnay, kan ku day haddii cusbooneysiintu kuu shaqayn weydo. +30 @@ -108,7 +106,6 @@ Si iskii ah usoo deji sidkanaha Qari tayada muqaalka aad dooratay natiijada waxa aad raadiso Raadin heer-sare ah - Keliya xogta waxaa ka dirayaa marka appku duqeeyo Xalqad buuxis ah tusi anemiga Dhiseyaasha appka sii beniin Way fashilantay dejintu, hubi ogolaanshaha isticmaalka kaydka @@ -176,7 +173,6 @@ Bixiyahan kuma shaqeeyo koromakaastiga Asalkiisii hore kusoo celi Dulucuda - Raalliahow, wuu duqeeyey appku, waxa warbixin cillad-saarka ka caawisa loo direy dhiseyaasha Kalka %1$s %2$d%3$s Xalqad @@ -453,7 +449,6 @@ Kii hore Is dhaafi fadhiisintan Beddel sida appku u muuqdo si uu ugu habboonaado moobilekaaga - Warbixinra cillad-saarka Maxaad rabtaa inaad daawato Dhan Ku dar kayd-weyne diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml index 2b6de30af6f..3bc16b041d2 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -40,7 +40,6 @@ Sub Radera fil Spela upp fil - Inaktivera automatisk felrapportering Mer information Göm @string/result_poster_img_des @@ -94,8 +93,6 @@ Information Avancerade sökresultat Presenterar sökresultaten i flera olika rader baserat på leverantören - Skickar endast data när appen kraschar - Skickar ingen data Visa appuppdateringar Sök automatiskt efter nya uppdateringar vid start. Uppdatera till beta-version @@ -112,7 +109,6 @@ Länken kopierades till urklipp Spela upp avsnitt Återställd till standardvärdet - Programmet kraschade tyvärr. En anonym felrapport kommer att skickas till utvecklarna Fel uppstod vid laddning av länkarna Säsong Ingen Säsong @@ -310,7 +306,6 @@ Slumpmässig Kommer snart… Filtrera efter föredraget språk - Kraschrapportering Kunde inte logga in på %s Utseende Layout diff --git a/app/src/main/res/values-b+ta/strings.xml b/app/src/main/res/values-b+ta/strings.xml index a71e4df2b1e..da13532299f 100644 --- a/app/src/main/res/values-b+ta/strings.xml +++ b/app/src/main/res/values-b+ta/strings.xml @@ -34,7 +34,6 @@ கோப்பை அழி கோப்பு இடைநிறுத்தம் பதிவிறக்கம் - தானியங்கி பிழை அறிக்கையை முடக்கு மேலும் செய்தி மறை அகற்று @@ -203,7 +202,6 @@ காப்பு அதிர்வெண் தரவு சேமிக்கப்பட்டது சேமிப்பக அனுமதிகள் இல்லை. தயவு செய்து மீண்டும் முயற்சிக்கவும். - செயலிழப்புகள் குறித்த தரவை மட்டுமே அனுப்புகிறது சுவரொட்டியில் இடைமுகம் கூறுகளை மாற்றவும் மேம்படுத்தல் சோதிக்க பூட்டு @@ -213,7 +211,6 @@ மூலம் கேம் சுவரொட்டி படம் - செயலிழப்பு அறிக்கை பொது பட்டியல் பதிப்பு நூலகத்தைத் தேர்ந்தெடுக்கவும் @@ -410,13 +407,11 @@ பிழையைப் பதிவிறக்குங்கள், சேமிப்பக அனுமதிகளை சரிபார்க்கவும் வழங்குநரை மாற்றவும் முன்னோட்டம் பின்னணி - எந்த தரவை அனுப்பவில்லை அனிமேசுக்கு நிரப்பு அத்தியாயத்தைக் காட்டு கூடுதல் களஞ்சியங்களிலிருந்து இன்னும் நிறுவப்படாத அனைத்து செருகுநிரல்களையும் தானாக நிறுவவும். முழு வெளியீடுகளுக்கு பதிலாக மட்டுமே புதுப்பிப்புகளைத் தேடுங்கள் அதே தேவ்சின் அனிம் பயன்பாடு அத்தியாயங்கள் - மன்னிக்கவும், விண்ணப்பம் செயலிழந்தது. ஒரு அநாமதேய பிழை அறிக்கை டெவலப்பர்களுக்கு அனுப்பப்படும் முடிந்தது வசன வரிகள் இல்லை கார்ட்டூன்கள் diff --git a/app/src/main/res/values-b+tl/strings.xml b/app/src/main/res/values-b+tl/strings.xml index 5fd25031e3d..87c15f52715 100644 --- a/app/src/main/res/values-b+tl/strings.xml +++ b/app/src/main/res/values-b+tl/strings.xml @@ -60,7 +60,6 @@ I-play ang file I-resume ang download I-pause ang download - Huwag awtomatikong mag-ulat ng bug More Info Itago I-play @@ -113,8 +112,6 @@ Impormasyon Adbans na maghanap Magbibigay ng pinaghiwa-hiwalay na resulta - Nagpapadala lamang ng data kung nag-crash ang app - Hindi magpapadala ng kahit anong data Ipakita ang filler episode sa anime Ipakita kung may bagong update Awtomatikong mag-check ng bagong update pagbukas ng app @@ -132,7 +129,6 @@ Link copied to clipboard Play Episode I-reset sa default value - Paumanhin, ang app ay nag-crash. Isang anonymous bug report ang ipapadala sa developers Season No season Episode diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index 34e702f9109..6f99401603f 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -84,7 +84,6 @@ Dosyayı Oynat İndirmeyi Sürdür İndirmeyi Duraklat - Otomatik hata raporlamayı kapat Daha fazla bilgi Gizle Oynat @@ -165,8 +164,6 @@ Bilgi Gelişmiş arama Arama sonuçlarını sağlayıcıya göre ayırır - Yalnızca çökmelerle ilgili verileri gönderir - Veri göndermez Anime için dolgu bölümünü göster Fragmanları göster Kitsu posterlerini göster @@ -188,7 +185,6 @@ Bağlantı panoya kopyalandı Bölümü oynat Varsayılan değere sıfırla - Üzgünüz, uygulama çöktü. Geliştiricilere anonim bir hata raporu gönderilecek Sezon %1$s %2$d%3$s Sezon yok @@ -427,7 +423,6 @@ Önceki Kurulumu atla Cihazınıza uygun uygulama görünümünü seçin - Çökme raporları Ne izlemek istiyorsunuz Bitti Eklentiler diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index eb7a2593084..5761cf31889 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -47,7 +47,6 @@ Суб. Видалити файл Відновити завантаження - Вимкнути автонадсилання звітів про помилки Приховати Переглянути Подробиці @@ -144,8 +143,6 @@ Подробиці Розширений пошук Показувати результати пошуку, розділені за постачальниками - Надсилати лише дані про збої - Не надсилати дані Показувати наповнювачі для аніме Показувати трейлери Приховати вибрану якість відео у результатах пошуку @@ -233,7 +230,6 @@ Дано бананів Рік +30 - Вибачте, у застосунку стався збій. Буде відправлено анонімне повідомлення про помилку розробникам %1$s %2$d%3$s Епізод %1$d-%2$d @@ -398,7 +394,6 @@ Далі Переглядайте відео на цих мовах Пропустити налаштування - Звітування про збої Що ви хочете побачити Готово Розширення diff --git a/app/src/main/res/values-b+ur/strings.xml b/app/src/main/res/values-b+ur/strings.xml index a6afb22de76..494e3118e7e 100644 --- a/app/src/main/res/values-b+ur/strings.xml +++ b/app/src/main/res/values-b+ur/strings.xml @@ -124,8 +124,6 @@ اکاؤنٹس اور سیکیورٹی معلومات آپ کو فراہم کنندہ کے ذریعہ علیحدہ کردہ تلاش کے نتائج فراہم کرتا ہے - صرف کریش پر ڈیٹا بھیجتا ہے - کوئی ڈیٹا نہیں بھیجتا تلاش کے نتائج میں منتخب ویڈیو کوالٹی چھپائیں خودکار پلگ ان اپ ڈیٹس ایپ کی تازہ کاریاں نمایش کریں @@ -156,7 +154,6 @@ ڈاؤن لوڈ ہو گیا انٹرنل سٹوریج ڈاؤن لوڈ کو روکیں - خودکار بگ رپورٹنگ کو غیر فعال کریں ذرائع کا استعمال کرتے ہوئے تلاش کریں اس فراہم کنندہ کو مناسب طریقے سے کام کرنے کے لیے VPN کی ضرورت پڑ سکتی ہے ڈیفالٹ سیٹنگز پر ری سیٹ کرنے کے لیے دبائیں اور تھامیں @@ -177,7 +174,6 @@ شامل کردہ ذخیروں سے خود بخود تمام ابھی تک انسٹال نہیں ہوئے پلگ ان انسٹال کریں۔ اپلیکیشن کو شروع کرنے کے بعد خود بخود نئی اپ ڈیٹس کی تلاش کریں۔ کچھ فون نئے پیکیج انسٹالر کو سپورٹ نہیں کرتے ہیں. اگر اپ ڈیٹس انسٹال نہیں ہوتے ہیں تو لیگیسی آپشن کو آزمائیں. - معذرت، ایپلی کیشن کریش ہو گئی. ایک گمنام بگ رپورٹ ڈویلپرز کو بھیجی جائے گی سیزن %1$s %2$d%3$s کوئی سیزن نہیں @@ -396,7 +392,6 @@ حوالہ دینے والا (مرضی پر) ان زبانوں میں ویڈیوز دیکھیں سیٹ اپ کو چھوڑ دیں - کریش رپورٹنگ ہو گیا ایکسٹینشنز ذخیرہ URL diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index b1e16612639..0a74744359e 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -72,7 +72,6 @@ Xem Tệp Tiếp tục tải Tạm dừng tải - Tắt tự động gửi dữ liệu khi xảy ra lỗi Thông tin thêm Ẩn Xem ngay @@ -152,8 +151,6 @@ Thông tin Tìm kiếm nâng cao Cho phép tìm kiếm theo bộ lọc từng nhà cung cấp - Chỉ gửi dữ liệu khi xảy ra lỗi đến nhà phát triển - Không gửi dữ liệu Hiển thị tập phụ cho anime Hiển thị trailer Hiển thị poster từ Kitsu @@ -175,7 +172,6 @@ Đã sao chép liên kết vào bộ nhớ tạm Xem Phim Thiết lập lại giá trị mặc định - Rất tiếc! Ứng dụng đã xảy ra lỗi. Một báo cáo lỗi ẩn danh sẽ được gửi đến nhà phát triển Mùa Không có mùa nào Tập @@ -395,7 +391,6 @@ Trước đó Bỏ qua cài đặt setup Chọn giao diện phù hợp với thiết bị của bạn - Báo cáo sự cố Bạn muốn xem gì Hoàn tất Tiện ích mở rộng diff --git a/app/src/main/res/values-b+zh+TW/strings.xml b/app/src/main/res/values-b+zh+TW/strings.xml index e4d7f9fa289..c29a8e206f2 100644 --- a/app/src/main/res/values-b+zh+TW/strings.xml +++ b/app/src/main/res/values-b+zh+TW/strings.xml @@ -84,7 +84,6 @@ 播放檔案 繼續下載 暫停下載 - 停用自動錯誤報告 更多資訊 隱藏 播放 @@ -165,8 +164,6 @@ 資訊 進階搜尋 為您提供按片源分開的搜尋結果 - 僅在程式崩潰時傳送相關資料 - 不傳送資料 顯示動畫外傳 顯示預告片 顯示來自 Kitsu 的封面 @@ -188,7 +185,6 @@ 連結已複製到剪貼簿 播放劇集 重設為預設值 - 很抱歉,應用程式崩潰了,將傳送一份匿名錯誤報告給開發者 %1$s %2$d%3$s 無季 @@ -428,7 +424,6 @@ 上一個 跳過設定 更改應用程式的外觀以適應你的設備 - 程式崩潰報告 你想要看什麼 完成 擴充功能 diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 4a5b8c1b293..9dfb9d90418 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -84,7 +84,6 @@ 播放文件 继续下载 暂停下载 - 禁用自动错误报告 更多信息 隐藏 播放 @@ -165,8 +164,6 @@ 信息 高级搜索 按片源分割搜索结果 - 仅发送关于崩溃的数据 - 不发送数据 显示动画外传 显示预告片 显示来自 Kitsu 的封面 @@ -189,7 +186,6 @@ 链接已复制到剪贴板 播放剧集 重置为默认值 - 抱歉,应用崩溃了,将发送一份匿名错误报告给开发者 %1$s %2$d%3$s 无季 @@ -429,7 +425,6 @@ 上一个 跳过设置向导 更改为适应您的设备的应用布局 - 崩溃报告 您想要看什么 完成 扩展 diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 9d1bfd1f6a3..10f61e40e8c 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -83,7 +83,6 @@ Reprodueix el fitxer Continua la descàrrega Posa la descàrrega en espera - Deshabilita el report automàtic d\'errors Més informació Amaga Reprodueix @@ -290,7 +289,6 @@ Anterior Omet la configuració Canvia l\'aspecte de la aplicació perquè s\'adapti al vostre dispositiu - Informe d\'errors Què vols veure Fet Extensions diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6fdfa0921e..2409c88787a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -179,7 +179,6 @@ Play File Resume Download Pause Download - Disable automatic bug reporting More info Hide Play @@ -273,8 +272,6 @@ Info Advanced Search Gives you the search results separated by provider - Only sends data on crashes - Sends no data Show filler episode for anime Show trailers Show posters from Kitsu @@ -302,9 +299,6 @@ Link copied to clipboard Play Episode Reset to default value - Sorry, the application crashed. An anonymous bug report will be sent to the - developers - Season %1$s %2$d%3$s No Season @@ -623,7 +617,6 @@ Previous Skip setup Change the look of the app to suit your device - Crash reporting What do you want to see Done Extensions diff --git a/app/src/main/res/xml/settings_updates.xml b/app/src/main/res/xml/settings_updates.xml index 2c2de04318c..3eb63b28fd9 100644 --- a/app/src/main/res/xml/settings_updates.xml +++ b/app/src/main/res/xml/settings_updates.xml @@ -84,12 +84,5 @@ android:icon="@drawable/ic_baseline_construction_24" android:title="@string/redo_setup_process" app:key="@string/redo_setup_key" /> - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 088cad6d5a0..d30e384ef75 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,4 @@ [versions] -acraCore = "5.13.1" activityKtx = "1.11.0" androidGradlePlugin = "8.13.1" appcompat = "1.7.1" @@ -54,8 +53,6 @@ compileSdk = "36" targetSdk = "36" [libraries] -acra-core = { module = "ch.acra:acra-core", version.ref = "acraCore" } -acra-toast = { module = "ch.acra:acra-toast", version.ref = "acraCore" } activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } From 009dcc2b895de671bde1f0dbc16dded63d490843 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:26:07 -0700 Subject: [PATCH 330/618] Use version catalog for plugins (#2206) --- app/build.gradle.kts | 6 +++--- build.gradle.kts | 27 ++++++++------------------- docs/build.gradle.kts | 6 +++--- gradle/libs.versions.toml | 13 +++++++++---- library/build.gradle.kts | 12 ++++++------ settings.gradle.kts | 24 ++++++++++++++++++++---- 6 files changed, 49 insertions(+), 39 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1a98ac2f36a..fb8f2e8362a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,9 +6,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { - id("com.android.application") - id("kotlin-android") - id("org.jetbrains.dokka") + alias(libs.plugins.android.application) + alias(libs.plugins.dokka) + alias(libs.plugins.kotlin.android) } val javaTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get()) diff --git a/build.gradle.kts b/build.gradle.kts index 22cdc4ba3df..a5c4f9fbcd1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,25 +1,14 @@ -buildscript { - repositories { - google() - mavenCentral() - } - - dependencies { - classpath(libs.android.gradle.plugin) - classpath(libs.buildkonfig.gradle.plugin) // Universal build config - classpath(libs.dokka.gradle.plugin) - classpath(libs.kotlin.gradle.plugin) - } +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.buildkonfig) apply false // Universal build config + alias(libs.plugins.dokka) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.jvm) apply false + alias(libs.plugins.kotlin.multiplatform) apply false } allprojects { - repositories { - google() - mavenCentral() - mavenLocal() - maven("https://jitpack.io") - } - // https://docs.gradle.org/current/userguide/upgrading_major_version_9.html#test_task_fails_when_no_tests_are_discovered tasks.withType().configureEach { failOnNoDiscoveredTests = false diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts index 203b938181e..8f5be2a2dbe 100644 --- a/docs/build.gradle.kts +++ b/docs/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("jvm") - id("org.jetbrains.dokka") + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.dokka) } dependencies { @@ -10,4 +10,4 @@ dependencies { dokka { moduleName = "Cloudstream" -} \ No newline at end of file +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d30e384ef75..fdef44ef888 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,3 +1,5 @@ +# https://docs.gradle.org/current/userguide/plugins.html#sec:version_catalog_plugin_application +# https://docs.gradle.org/current/userguide/dependency_versions.html#sec:strict-version [versions] activityKtx = "1.11.0" androidGradlePlugin = "8.13.1" @@ -54,10 +56,8 @@ targetSdk = "36" [libraries] activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } -android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" } -buildkonfig-gradle-plugin = { module = "com.codingfeline.buildkonfig:buildkonfig-gradle-plugin", version.ref = "buildkonfigGradlePlugin" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } colorpicker = { module = "com.github.recloudstream:color-picker-android", version.ref = "colorpicker" } @@ -67,7 +67,6 @@ core = { module = "androidx.test:core" } core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } databinding = { module = "androidx.databinding:viewbinding", version.ref = "androidGradlePlugin" } desugar_jdk_libs_nio = { module = "com.android.tools:desugar_jdk_libs_nio", version.ref = "desugar_jdk_libs_nio" } -dokka-gradle-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokkaGradlePlugin" } espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } ext-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" } fuzzywuzzy = { module = "me.xdrop:fuzzywuzzy", version.ref = "fuzzywuzzy" } @@ -77,7 +76,6 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit = { module = "junit:junit", version.ref = "junit" } junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "junitKtx" } juniversalchardet = { module = "com.github.albfernandez:juniversalchardet", version.ref = "juniversalchardet" } -kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinGradlePlugin" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" } lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } @@ -116,6 +114,13 @@ work-runtime = { module = "androidx.work:work-runtime", version.ref = "workRunti work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } [plugins] +android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } +buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfigGradlePlugin" } +dokka = { id = "org.jetbrains.dokka", version.ref = "dokkaGradlePlugin" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinGradlePlugin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm" , version.ref = "kotlinGradlePlugin" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlinGradlePlugin" } [bundles] media3 = ["media3-cast", "media3-common", "media3-container", "media3-datasource-cronet", "media3-datasource-okhttp", "media3-exoplayer", "media3-exoplayer-dash", "media3-exoplayer-hls", "media3-session", "media3-ui"] diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 3fc1ce8dd6f..a418efaab0a 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -6,11 +6,11 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { - kotlin("multiplatform") - id("maven-publish") - id("com.android.library") - id("com.codingfeline.buildkonfig") - id("org.jetbrains.dokka") + id("maven-publish") // Gradle core plugin + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) + alias(libs.plugins.buildkonfig) + alias(libs.plugins.dokka) } val javaTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get()) @@ -125,4 +125,4 @@ dokka { } } } -} \ No newline at end of file +} diff --git a/settings.gradle.kts b/settings.gradle.kts index bd26f9f3499..73bf5a1958b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,21 @@ -rootProject.name = "CloudStream" +// https://developer.android.com/build#settings-file +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + mavenLocal() + maven("https://jitpack.io") + } +} -include(":app") -include(":library") -include(":docs") \ No newline at end of file +rootProject.name = "CloudStream" +include(":app", ":library", ":docs") From 7f9f89cbf66a5a48778d54f45245b8ece959fc60 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:16:37 -0700 Subject: [PATCH 331/618] Use version catalog bundles for coil and lifecycle (#2237) --- app/build.gradle.kts | 14 ++++++-------- gradle/libs.versions.toml | 13 +++++++------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fb8f2e8362a..385b4af6dad 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -173,9 +173,8 @@ dependencies { implementation(libs.core.ktx) implementation(libs.activity.ktx) implementation(libs.appcompat) - implementation(libs.bundles.navigationKtx) - implementation(libs.lifecycle.livedata.ktx) - implementation(libs.lifecycle.viewmodel.ktx) + implementation(libs.bundles.lifecycle) + implementation(libs.bundles.navigation) // Design & UI implementation(libs.preference.ktx) @@ -184,21 +183,20 @@ dependencies { implementation(libs.swiperefreshlayout) // Coil Image Loading - implementation(libs.coil) - implementation(libs.coil.network.okhttp) + implementation(libs.bundles.coil) // Media 3 (ExoPlayer) implementation(libs.bundles.media3) implementation(libs.video) + // FFmpeg Decoding + implementation(libs.bundles.nextlib) + // PlayBack implementation(libs.colorpicker) // Subtitle Color Picker implementation(libs.newpipeextractor) // For Trailers implementation(libs.juniversalchardet) // Subtitle Decoding - // FFmpeg Decoding - implementation(libs.bundles.nextlibMedia3) - // UI Stuff implementation(libs.shimmer) // Shimmering Effect (Loading Skeleton) implementation(libs.palette.ktx) // Palette for Images -> Colors diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fdef44ef888..4ebe1ce2de1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,8 +24,7 @@ junitVersion = "1.3.0" juniversalchardet = "2.5.0" kotlinGradlePlugin = "2.2.21" kotlinxCoroutinesCore = "1.10.2" -lifecycleLivedataKtx = "2.9.4" -lifecycleViewmodelKtx = "2.9.4" +lifecycleKtx = "2.9.4" material = "1.14.0-alpha06" media3 = "1.8.0" navigationKtx = "2.9.6" @@ -77,8 +76,8 @@ junit = { module = "junit:junit", version.ref = "junit" } junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "junitKtx" } juniversalchardet = { module = "com.github.albfernandez:juniversalchardet", version.ref = "juniversalchardet" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } -lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" } -lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } +lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleKtx" } +lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleKtx" } material = { module = "com.google.android.material:material", version.ref = "material" } media3-cast = { module = "androidx.media3:media3-cast", version.ref = "media3" } media3-common = { module = "androidx.media3:media3-common", version.ref = "media3" } @@ -123,6 +122,8 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm" , version.ref = "kotlinGradlePlug kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlinGradlePlugin" } [bundles] +coil = ["coil", "coil-network-okhttp"] +lifecycle = ["lifecycle-livedata-ktx", "lifecycle-viewmodel-ktx"] media3 = ["media3-cast", "media3-common", "media3-container", "media3-datasource-cronet", "media3-datasource-okhttp", "media3-exoplayer", "media3-exoplayer-dash", "media3-exoplayer-hls", "media3-session", "media3-ui"] -navigationKtx = ["navigation-fragment-ktx", "navigation-ui-ktx"] -nextlibMedia3 = ["nextlib-media3ext", "nextlib-mediainfo"] +navigation = ["navigation-fragment-ktx", "navigation-ui-ktx"] +nextlib = ["nextlib-media3ext", "nextlib-mediainfo"] From 9d651f1f8264d008540fb7eb9bdf35d80b34c41d Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:24:21 -0700 Subject: [PATCH 332/618] Remove work-runtime dependency (#2234) We only really need to include the Kotlin version, work-runtime-ktx here. --- app/build.gradle.kts | 1 - gradle/libs.versions.toml | 2 -- 2 files changed, 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 385b4af6dad..6cc80159130 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -220,7 +220,6 @@ dependencies { implementation(libs.torrentserver) // Downloading & Networking - implementation(libs.work.runtime) implementation(libs.work.runtime.ktx) implementation(libs.nicehttp) // HTTP Lib diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ebe1ce2de1..8e22a64b2f9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,7 +44,6 @@ tmdbJava = "2.13.0" torrentserver = "7861970e038b35cd8c6918384e49caf26903e09e" tvprovider = "1.1.0" video = "1.0.0" -workRuntime = "2.10.5" workRuntimeKtx = "2.10.5" jvmTarget = "1.8" @@ -109,7 +108,6 @@ tmdb-java = { module = "com.uwetrottmann.tmdb2:tmdb-java", version.ref = "tmdbJa torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref = "torrentserver" } tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } video = { module = "com.google.android.mediahome:video", version.ref = "video" } -work-runtime = { module = "androidx.work:work-runtime", version.ref = "workRuntime" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } [plugins] From d43a371b15eb6806cb549adaeeda343ec939cd92 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:34:14 -0700 Subject: [PATCH 333/618] Better backward compatibility for AcraApplication (#2265) --- .../lagradost/cloudstream3/AcraApplication.kt | 48 ++++++++++++++----- .../lagradost/cloudstream3/CloudStreamApp.kt | 2 + 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index 92993f78b37..262f5752230 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -1,5 +1,11 @@ package com.lagradost.cloudstream3 +import android.content.Context +import com.lagradost.api.setContext +import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.setKey +import java.lang.ref.WeakReference + /** * Deprecated alias for CloudStreamApp for backwards compatibility with plugins. * Use CloudStreamApp instead. @@ -12,61 +18,77 @@ package com.lagradost.cloudstream3 level = DeprecationLevel.WARNING )*/ class AcraApplication { + // All methods here can be changed to be a wrapper around CloudStream app + // without a seperate deprecation after next stable. All methods should + // also be deprecated at that time. companion object { + // This can be removed without deprecation after next stable + private var _context: WeakReference? = null /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.context"), level = DeprecationLevel.WARNING )*/ - val context get() = CloudStreamApp.context + var context + get() = _context?.get() + internal set(value) { + _context = WeakReference(value) + setContext(WeakReference(value)) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(path, value)"), level = DeprecationLevel.WARNING )*/ - fun setKey(path: String, value: T) = - CloudStreamApp.setKey(path, value) + fun setKey(path: String, value: T) { + context?.setKey(path, value) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(folder, path, value)"), level = DeprecationLevel.WARNING )*/ - fun setKey(folder: String, path: String, value: T) = - CloudStreamApp.setKey(folder, path, value) + fun setKey(folder: String, path: String, value: T) { + context?.setKey(folder, path, value) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(path, defVal)"), level = DeprecationLevel.WARNING )*/ - inline fun getKey(path: String, defVal: T?): T? = - CloudStreamApp.getKey(path, defVal) + inline fun getKey(path: String, defVal: T?): T? { + return context?.getKey(path, defVal) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(path)"), level = DeprecationLevel.WARNING )*/ - inline fun getKey(path: String): T? = - CloudStreamApp.getKey(path) + inline fun getKey(path: String): T? { + return context?.getKey(path) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(folder, path)"), level = DeprecationLevel.WARNING )*/ - inline fun getKey(folder: String, path: String): T? = - CloudStreamApp.getKey(folder, path) + inline fun getKey(folder: String, path: String): T? { + return context?.getKey(folder, path) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(folder, path, defVal)"), level = DeprecationLevel.WARNING )*/ - inline fun getKey(folder: String, path: String, defVal: T?): T? = - CloudStreamApp.getKey(folder, path, defVal) + inline fun getKey(folder: String, path: String, defVal: T?): T? { + return context?.getKey(folder, path, defVal) + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt index 6421f38c21e..b7832799884 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt @@ -86,6 +86,8 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory { override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) context = base + // This can be removed without deprecation after next stable + AcraApplication.context = context } override fun newImageLoader(context: PlatformContext): ImageLoader { From 7fb6f3f5353306699f9a9779f96cd8797a37f5b8 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:37:47 -0700 Subject: [PATCH 334/618] Add explicit dependsOn for copyJar (#2261) --- .github/workflows/prerelease.yml | 4 +--- app/build.gradle.kts | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 62fd571f78e..164a8458e95 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -48,9 +48,7 @@ jobs: echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT - name: Run Gradle - run: | - ./gradlew assemblePrerelease build androidSourcesJar - ./gradlew makeJar # for classes.jar, has to be done after assemblePrerelease + run: ./gradlew assemblePrerelease build androidSourcesJar makeJar env: SIGNING_KEY_ALIAS: "key0" SIGNING_KEY_PASSWORD: ${{ steps.fetch_keystore.outputs.key_pwd }} diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6cc80159130..c0e55071baa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -241,6 +241,7 @@ tasks.register("androidSourcesJar") { } tasks.register("copyJar") { + dependsOn("build", ":library:jvmJar") from( "build/intermediates/compile_app_classes_jar/prereleaseDebug/bundlePrereleaseDebugClassesToCompileJar", "../library/build/libs" From 38296bfb1a791d07e4611068b292a2537c51dbad Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:24:31 +0100 Subject: [PATCH 335/618] Fixed the atrocity of download selection along with some crash fixes and bugs. --- .../lagradost/cloudstream3/ui/BaseFragment.kt | 29 +- .../ui/download/DownloadAdapter.kt | 461 +++++++++--------- .../ui/download/DownloadChildFragment.kt | 45 +- .../ui/download/DownloadFragment.kt | 42 +- .../ui/download/DownloadViewModel.kt | 178 ++++--- .../ui/settings/SettingsFragment.kt | 3 +- .../ui/settings/extensions/PluginsFragment.kt | 4 +- .../cloudstream3/utils/ConsistentLiveData.kt | 44 ++ 8 files changed, 450 insertions(+), 356 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt index 14901dda24a..72955e7cf8c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt @@ -126,6 +126,33 @@ abstract class BaseFragment( ) : Fragment(), BaseFragmentHelper { override var _binding: T? = null + /** Safer activity?.onBackPressedDispatcher?.onBackPressed() with fallback behavior instead of app crash */ + fun dispatchBackPressed() { + try { + activity?.onBackPressedDispatcher?.onBackPressed() + } catch (_: IllegalStateException) { + // FragmentManager is already executing transactions, so try again + delayedDispatchBackPressed(5) + } catch (t: Throwable) { + logError(t) + } + } + + /** Recursive back press when available */ + private fun delayedDispatchBackPressed(remaining: Int) { + if (remaining <= 0) return + binding?.root?.postDelayed({ + try { + activity?.onBackPressedDispatcher?.onBackPressed() + } catch (_: IllegalStateException) { + // FragmentManager is already executing transactions, so try again + delayedDispatchBackPressed(remaining - 1) + } catch (t: Throwable) { + logError(t) + } + }, 200) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -238,7 +265,7 @@ abstract class BaseBottomSheetDialogFragment( } } -abstract class BasePreferenceFragmentCompat(): PreferenceFragmentCompat() { +abstract class BasePreferenceFragmentCompat() : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setSystemBarsPadding() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt index e6daf0f2f1a..b26e99d81b2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.download +import android.annotation.SuppressLint import android.text.format.Formatter.formatShortFileSize import android.view.LayoutInflater import android.view.ViewGroup @@ -7,16 +8,15 @@ import android.widget.CheckBox import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.NoStateAdapter +import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.download.button.DownloadStatusTell import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull -import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper @@ -69,7 +69,7 @@ class DownloadAdapter( private val onHeaderClickEvent: (DownloadHeaderClickEvent) -> Unit, private val onItemClickEvent: (DownloadClickEvent) -> Unit, private val onItemSelectionChanged: (Int, Boolean) -> Unit, -) : ListAdapter(DiffCallback()) { +) : NoStateAdapter(DiffCallback()) { private var isMultiDeleteState: Boolean = false @@ -78,97 +78,194 @@ class DownloadAdapter( private const val VIEW_TYPE_CHILD = 1 } - inner class DownloadViewHolder( - private val binding: ViewBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(card: VisualDownloadCached?) { - when (binding) { - is DownloadHeaderEpisodeBinding -> bindHeader(card as? VisualDownloadCached.Header) - is DownloadChildEpisodeBinding -> bindChild(card as? VisualDownloadCached.Child) - } - } + private fun bindHeader(binding: ViewBinding, card: VisualDownloadCached.Header?) { + if (binding !is DownloadHeaderEpisodeBinding || card == null) return - private fun bindHeader(card: VisualDownloadCached.Header?) { - if (binding !is DownloadHeaderEpisodeBinding || card == null) return - - val data = card.data - binding.apply { - episodeHolder.apply { - if (isMultiDeleteState) { - setOnClickListener { - toggleIsChecked(deleteCheckbox, data.id) - } + val data = card.data + binding.apply { + episodeHolder.apply { + if (isMultiDeleteState) { + setOnClickListener { + toggleIsChecked(deleteCheckbox, data.id) } - setOnLongClickListener { toggleIsChecked(deleteCheckbox, data.id) true } + } else { + setOnLongClickListener { + onItemSelectionChanged.invoke(data.id, true) + true + } } + } - downloadHeaderPoster.apply { - loadImage(data.poster) - if (isMultiDeleteState) { - setOnClickListener { - toggleIsChecked(deleteCheckbox, data.id) - } - } else { - setOnClickListener { - onHeaderClickEvent.invoke( - DownloadHeaderClickEvent( - DOWNLOAD_ACTION_LOAD_RESULT, - data - ) + downloadHeaderPoster.apply { + loadImage(data.poster) + if (isMultiDeleteState) { + setOnClickListener { + toggleIsChecked(deleteCheckbox, data.id) + } + } else { + setOnClickListener { + onHeaderClickEvent.invoke( + DownloadHeaderClickEvent( + DOWNLOAD_ACTION_LOAD_RESULT, + data ) - } + ) } + } - setOnLongClickListener { - toggleIsChecked(deleteCheckbox, data.id) - true - } + setOnLongClickListener { + toggleIsChecked(deleteCheckbox, data.id) + true } - downloadHeaderTitle.text = data.name - val formattedSize = formatShortFileSize(itemView.context, card.totalBytes) + } + downloadHeaderTitle.text = data.name + val formattedSize = formatShortFileSize(binding.root.context, card.totalBytes) - if (card.child != null) { - handleChildDownload(card, formattedSize) - } else handleParentDownload(card, formattedSize) + if (card.child != null) { + handleChildDownload(card, formattedSize) + } else handleParentDownload(card, formattedSize) - if (isMultiDeleteState) { - deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> - onItemSelectionChanged.invoke(data.id, isChecked) - } - } else deleteCheckbox.setOnCheckedChangeListener(null) + if (isMultiDeleteState) { + deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> + onItemSelectionChanged.invoke(data.id, isChecked) + } + } else deleteCheckbox.setOnCheckedChangeListener(null) - deleteCheckbox.apply { - isVisible = isMultiDeleteState - isChecked = card.isSelected + deleteCheckbox.apply { + isVisible = isMultiDeleteState + isChecked = card.isSelected + } + } + } + + private fun DownloadHeaderEpisodeBinding.handleChildDownload( + card: VisualDownloadCached.Header, + formattedSize: String + ) { + card.child ?: return + downloadHeaderGotoChild.isVisible = false + + val posDur = getViewPos(card.data.id) + watchProgressContainer.isVisible = true + downloadHeaderEpisodeProgress.apply { + isVisible = posDur != null + posDur?.let { + val max = (it.duration / 1000).toInt() + val progress = (it.position / 1000).toInt() + + if (max > 0 && progress >= (0.95 * max).toInt()) { + playIcon.setImageResource(R.drawable.ic_baseline_check_24) + isVisible = false + } else { + playIcon.setImageResource(R.drawable.netflix_play) + this.max = max + this.progress = progress + isVisible = true } } } - private fun DownloadHeaderEpisodeBinding.handleChildDownload( - card: VisualDownloadCached.Header, - formattedSize: String - ) { - card.child ?: return - downloadHeaderGotoChild.isVisible = false + val status = downloadButton.getStatus(card.child.id, card.currentBytes, card.totalBytes) + if (status == DownloadStatusTell.IsDone) { + // We do this here instead if we are finished downloading + // so that we can use the value from the view model + // rather than extra unneeded disk operations and to prevent a + // delay in updating download icon state. + downloadButton.setProgress(card.currentBytes, card.totalBytes) + downloadButton.applyMetaData(card.child.id, card.currentBytes, card.totalBytes) + // We will let the view model handle this + downloadButton.doSetProgress = false + downloadButton.progressBar.progressDrawable = + downloadButton.getDrawableFromStatus(status) + ?.let { ContextCompat.getDrawable(downloadButton.context, it) } + downloadHeaderInfo.text = formattedSize + } else { + // We need to make sure we restore the correct progress + // when we refresh data in the adapter. + downloadButton.resetView() + val drawable = downloadButton.getDrawableFromStatus(status)?.let { + ContextCompat.getDrawable(downloadButton.context, it) + } + downloadButton.statusView.setImageDrawable(drawable) + downloadButton.progressBar.progressDrawable = + ContextCompat.getDrawable( + downloadButton.context, + downloadButton.progressDrawable + ) + } + + downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, onItemClickEvent) + downloadButton.isVisible = !isMultiDeleteState + + if (!isMultiDeleteState) { + episodeHolder.setOnClickListener { + onItemClickEvent.invoke( + DownloadClickEvent( + DOWNLOAD_ACTION_PLAY_FILE, + card.child + ) + ) + } + } + } + + private fun DownloadHeaderEpisodeBinding.handleParentDownload( + card: VisualDownloadCached.Header, + formattedSize: String + ) { + downloadButton.isVisible = false + downloadHeaderEpisodeProgress.isVisible = false + downloadHeaderGotoChild.isVisible = !isMultiDeleteState + + try { + downloadHeaderInfo.text = + downloadHeaderInfo.context.getString(R.string.extra_info_format).format( + card.totalDownloads, + downloadHeaderInfo.context.resources.getQuantityString( + R.plurals.episodes, + card.totalDownloads + ), + formattedSize + ) + } catch (e: Exception) { + downloadHeaderInfo.text = null + logError(e) + } + + if (!isMultiDeleteState) { + episodeHolder.setOnClickListener { + onHeaderClickEvent.invoke( + DownloadHeaderClickEvent( + DOWNLOAD_ACTION_GO_TO_CHILD, + card.data + ) + ) + } + } + } + + private fun bindChild(binding: ViewBinding, card: VisualDownloadCached.Child?) { + if (binding !is DownloadChildEpisodeBinding || card == null) return - val posDur = getViewPos(card.data.id) - watchProgressContainer.isVisible = true - downloadHeaderEpisodeProgress.apply { + val data = card.data + binding.apply { + val posDur = getViewPos(data.id) + downloadChildEpisodeProgress.apply { isVisible = posDur != null posDur?.let { val max = (it.duration / 1000).toInt() val progress = (it.position / 1000).toInt() if (max > 0 && progress >= (0.95 * max).toInt()) { - playIcon.setImageResource(R.drawable.ic_baseline_check_24) + downloadChildEpisodePlay.setImageResource(R.drawable.ic_baseline_check_24) isVisible = false } else { - playIcon.setImageResource(R.drawable.netflix_play) + downloadChildEpisodePlay.setImageResource(R.drawable.play_button_transparent) this.max = max this.progress = progress isVisible = true @@ -176,20 +273,21 @@ class DownloadAdapter( } } - val status = downloadButton.getStatus(card.child.id, card.currentBytes, card.totalBytes) + val status = downloadButton.getStatus(data.id, card.currentBytes, card.totalBytes) if (status == DownloadStatusTell.IsDone) { // We do this here instead if we are finished downloading // so that we can use the value from the view model // rather than extra unneeded disk operations and to prevent a // delay in updating download icon state. downloadButton.setProgress(card.currentBytes, card.totalBytes) - downloadButton.applyMetaData(card.child.id, card.currentBytes, card.totalBytes) + downloadButton.applyMetaData(data.id, card.currentBytes, card.totalBytes) // We will let the view model handle this downloadButton.doSetProgress = false downloadButton.progressBar.progressDrawable = downloadButton.getDrawableFromStatus(status) ?.let { ContextCompat.getDrawable(downloadButton.context, it) } - downloadHeaderInfo.text = formattedSize + downloadChildEpisodeTextExtra.text = + formatShortFileSize(downloadChildEpisodeTextExtra.context, card.totalBytes) } else { // We need to make sure we restore the correct progress // when we refresh data in the adapter. @@ -205,208 +303,105 @@ class DownloadAdapter( ) } - downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, onItemClickEvent) + downloadButton.setDefaultClickListener( + data, + downloadChildEpisodeTextExtra, + onItemClickEvent + ) downloadButton.isVisible = !isMultiDeleteState - if (!isMultiDeleteState) { - episodeHolder.setOnClickListener { - onItemClickEvent.invoke( - DownloadClickEvent( - DOWNLOAD_ACTION_PLAY_FILE, - card.child - ) - ) - } - } - } - - private fun DownloadHeaderEpisodeBinding.handleParentDownload( - card: VisualDownloadCached.Header, - formattedSize: String - ) { - downloadButton.isVisible = false - downloadHeaderEpisodeProgress.isVisible = false - downloadHeaderGotoChild.isVisible = !isMultiDeleteState - - try { - downloadHeaderInfo.text = - downloadHeaderInfo.context.getString(R.string.extra_info_format).format( - card.totalDownloads, - downloadHeaderInfo.context.resources.getQuantityString( - R.plurals.episodes, - card.totalDownloads - ), - formattedSize - ) - } catch (e: Exception) { - downloadHeaderInfo.text = null - logError(e) + downloadChildEpisodeText.apply { + text = context.getNameFull(data.name, data.episode, data.season) + isSelected = true // Needed for text repeating } - if (!isMultiDeleteState) { - episodeHolder.setOnClickListener { - onHeaderClickEvent.invoke( - DownloadHeaderClickEvent( - DOWNLOAD_ACTION_GO_TO_CHILD, - card.data - ) - ) - } + downloadChildEpisodeHolder.setOnClickListener { + onItemClickEvent.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, data)) } - } - private fun bindChild(card: VisualDownloadCached.Child?) { - if (binding !is DownloadChildEpisodeBinding || card == null) return - - val data = card.data - binding.apply { - val posDur = getViewPos(data.id) - downloadChildEpisodeProgress.apply { - isVisible = posDur != null - posDur?.let { - val max = (it.duration / 1000).toInt() - val progress = (it.position / 1000).toInt() - - if (max > 0 && progress >= (0.95 * max).toInt()) { - downloadChildEpisodePlay.setImageResource(R.drawable.ic_baseline_check_24) - isVisible = false - } else { - downloadChildEpisodePlay.setImageResource(R.drawable.play_button_transparent) - this.max = max - this.progress = progress - isVisible = true + downloadChildEpisodeHolder.apply { + when { + isMultiDeleteState -> { + setOnClickListener { + toggleIsChecked(deleteCheckbox, data.id) } - } - } - - val status = downloadButton.getStatus(data.id, card.currentBytes, card.totalBytes) - if (status == DownloadStatusTell.IsDone) { - // We do this here instead if we are finished downloading - // so that we can use the value from the view model - // rather than extra unneeded disk operations and to prevent a - // delay in updating download icon state. - downloadButton.setProgress(card.currentBytes, card.totalBytes) - downloadButton.applyMetaData(data.id, card.currentBytes, card.totalBytes) - // We will let the view model handle this - downloadButton.doSetProgress = false - downloadButton.progressBar.progressDrawable = - downloadButton.getDrawableFromStatus(status) - ?.let { ContextCompat.getDrawable(downloadButton.context, it) } - downloadChildEpisodeTextExtra.text = - formatShortFileSize(downloadChildEpisodeTextExtra.context, card.totalBytes) - } else { - // We need to make sure we restore the correct progress - // when we refresh data in the adapter. - downloadButton.resetView() - val drawable = downloadButton.getDrawableFromStatus(status)?.let { - ContextCompat.getDrawable(downloadButton.context, it) - } - downloadButton.statusView.setImageDrawable(drawable) - downloadButton.progressBar.progressDrawable = - ContextCompat.getDrawable( - downloadButton.context, - downloadButton.progressDrawable - ) - } - - downloadButton.setDefaultClickListener( - data, - downloadChildEpisodeTextExtra, - onItemClickEvent - ) - downloadButton.isVisible = !isMultiDeleteState - - downloadChildEpisodeText.apply { - text = context.getNameFull(data.name, data.episode, data.season) - isSelected = true // Needed for text repeating - } - - downloadChildEpisodeHolder.setOnClickListener { - onItemClickEvent.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, data)) - } - - downloadChildEpisodeHolder.apply { - when { - isMultiDeleteState -> { - setOnClickListener { - toggleIsChecked(deleteCheckbox, data.id) - } + setOnLongClickListener { + toggleIsChecked(deleteCheckbox, data.id) + true } + } - else -> { - setOnClickListener { - onItemClickEvent.invoke( - DownloadClickEvent( - DOWNLOAD_ACTION_PLAY_FILE, - data - ) + else -> { + setOnClickListener { + onItemClickEvent.invoke( + DownloadClickEvent( + DOWNLOAD_ACTION_PLAY_FILE, + data ) - } + ) } - } - setOnLongClickListener { - toggleIsChecked(deleteCheckbox, data.id) - true + setOnLongClickListener { + onItemSelectionChanged.invoke(data.id, true) + true + } } } + } - if (isMultiDeleteState) { - deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> - onItemSelectionChanged.invoke(data.id, isChecked) - } - } else deleteCheckbox.setOnCheckedChangeListener(null) - - deleteCheckbox.apply { - isVisible = isMultiDeleteState - isChecked = card.isSelected + if (isMultiDeleteState) { + deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> + onItemSelectionChanged.invoke(data.id, isChecked) } + } else deleteCheckbox.setOnCheckedChangeListener(null) + + deleteCheckbox.apply { + isVisible = isMultiDeleteState + isChecked = card.isSelected } } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder { + override fun onCreateCustomContent(parent: ViewGroup, viewType: Int): ViewHolderState { val inflater = LayoutInflater.from(parent.context) val binding = when (viewType) { VIEW_TYPE_HEADER -> DownloadHeaderEpisodeBinding.inflate(inflater, parent, false) VIEW_TYPE_CHILD -> DownloadChildEpisodeBinding.inflate(inflater, parent, false) else -> throw IllegalArgumentException("Invalid view type") } - return DownloadViewHolder(binding) + return ViewHolderState(binding) } - override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) { - holder.bind(getItem(position)) + override fun onBindContent( + holder: ViewHolderState, + item: VisualDownloadCached, + position: Int + ) { + when (val binding = holder.view) { + is DownloadHeaderEpisodeBinding -> bindHeader( + binding, + item as? VisualDownloadCached.Header + ) + + is DownloadChildEpisodeBinding -> bindChild( + binding, + item as? VisualDownloadCached.Child + ) + } } - override fun getItemViewType(position: Int): Int { - return when (getItem(position)) { + override fun customContentViewType(item: VisualDownloadCached): Int { + return when (item) { is VisualDownloadCached.Child -> VIEW_TYPE_CHILD is VisualDownloadCached.Header -> VIEW_TYPE_HEADER - else -> throw IllegalArgumentException("Invalid data type at position $position") } } + @SuppressLint("NotifyDataSetChanged") fun setIsMultiDeleteState(value: Boolean) { if (isMultiDeleteState == value) return isMultiDeleteState = value - notifyItemRangeChanged(0, itemCount) - } - - fun notifyAllSelected() { - currentList.indices.forEach { index -> - if (!currentList[index].isSelected) { - notifyItemChanged(index) - } - } - } - - fun notifySelectionStates() { - currentList.indices.forEach { index -> - if (currentList[index].isSelected) { - notifyItemChanged(index) - } - } + notifyDataSetChanged() // This is shit, but what can you do? } private fun toggleIsChecked(checkbox: CheckBox, itemId: Int) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt index 980d08a9e3f..08194fd31cb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt @@ -9,6 +9,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding +import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.BaseFragment import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick @@ -41,6 +42,7 @@ class DownloadChildFragment : BaseFragment( override fun onDestroyView() { activity?.detachBackPressedCallback("Downloads") + downloadViewModel.clearChildren() super.onDestroyView() } @@ -68,27 +70,22 @@ class DownloadChildFragment : BaseFragment( downloadViewModel.setIsMultiDeleteState(false) } - /** - * We have to make sure selected items are - * cleared here as well so we don't run in an - * inconsistent state where selected items do - * not match the multi delete state we are in. - */ - downloadViewModel.clearSelectedItems() val folder = arguments?.getString("folder") val name = arguments?.getString("name") if (folder == null) { - activity?.onBackPressedDispatcher?.onBackPressed() + dispatchBackPressed() return } + context?.let { downloadViewModel.updateChildList(it, folder) } + binding.downloadChildToolbar.apply { title = name if (isLayout(PHONE or EMULATOR)) { setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) setNavigationOnClickListener { - activity?.onBackPressedDispatcher?.onBackPressed() + dispatchBackPressed() } } setAppBarNoScrollFlagsOnTV() @@ -96,13 +93,18 @@ class DownloadChildFragment : BaseFragment( binding.downloadDeleteAppbar.setAppBarNoScrollFlagsOnTV() - observe(downloadViewModel.childCards) { - if (it.isEmpty()) { - activity?.onBackPressedDispatcher?.onBackPressed() - return@observe + observe(downloadViewModel.childCards) { cards -> + when (cards) { + is Resource.Success -> { + if (cards.value.isEmpty()) { + dispatchBackPressed() + } + (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(cards.value) + } + else -> { + (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(null) + } } - - (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(it) } observe(downloadViewModel.isMultiDeleteState) { isMultiDeleteState -> val adapter = binding.downloadChildList.adapter as? DownloadAdapter @@ -124,7 +126,7 @@ class DownloadChildFragment : BaseFragment( binding.btnDelete.isVisible = it.isNotEmpty() binding.selectItemsText.isVisible = it.isEmpty() - val allSelected = downloadViewModel.isAllSelected() + val allSelected = downloadViewModel.isAllChildrenSelected() if (allSelected) { binding.btnToggleAll.setText(R.string.deselect_all) } else binding.btnToggleAll.setText(R.string.select_all) @@ -156,11 +158,9 @@ class DownloadChildFragment : BaseFragment( nextDown = FOCUS_SELF, ) } - - context?.let { downloadViewModel.updateChildList(it, folder) } } - private fun handleSelectedChange(selected: MutableSet) { + private fun handleSelectedChange(selected: Set) { if (selected.isNotEmpty()) { binding?.downloadDeleteAppbar?.isVisible = true binding?.downloadChildToolbar?.isVisible = false @@ -179,14 +179,11 @@ class DownloadChildFragment : BaseFragment( } binding?.btnToggleAll?.setOnClickListener { - val allSelected = downloadViewModel.isAllSelected() - val adapter = binding?.downloadChildList?.adapter as? DownloadAdapter + val allSelected = downloadViewModel.isAllChildrenSelected() if (allSelected) { - adapter?.notifySelectionStates() downloadViewModel.clearSelectedItems() } else { - adapter?.notifyAllSelected() - downloadViewModel.selectAllItems() + downloadViewModel.selectAllChildren() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index 46bb0c7dc79..e3d77abac9c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -25,6 +25,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding import com.lagradost.cloudstream3.databinding.StreamInputBinding import com.lagradost.cloudstream3.isEpisodeBased +import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.BaseFragment @@ -101,19 +102,27 @@ class DownloadFragment : BaseFragment( downloadViewModel.setIsMultiDeleteState(false) } - /** - * We have to make sure selected items are - * cleared here as well so we don't run in an - * inconsistent state where selected items do - * not match the multi delete state we are in. - */ - downloadViewModel.clearSelectedItems() + observe(downloadViewModel.headerCards) { cards -> + when (cards) { + is Resource.Success -> { + (binding.downloadList.adapter as? DownloadAdapter)?.submitList(cards.value) + binding.textNoDownloads.isVisible = cards.value.isEmpty() + binding.downloadLoading.isVisible = false + binding.downloadList.isVisible = true + } + + is Resource.Loading -> { + binding.downloadList.isVisible = false + binding.downloadLoading.isVisible = true + } - observe(downloadViewModel.headerCards) { - (binding.downloadList.adapter as? DownloadAdapter)?.submitList(it) - binding.downloadLoading.isVisible = false - binding.textNoDownloads.isVisible = it.isEmpty() + is Resource.Failure -> { + binding.downloadList.isVisible = true + binding.downloadLoading.isVisible = false + } + } } + observe(downloadViewModel.availableBytes) { updateStorageInfo( binding.root.context, @@ -173,7 +182,7 @@ class DownloadFragment : BaseFragment( binding.btnDelete.isVisible = it.isNotEmpty() binding.selectItemsText.isVisible = it.isEmpty() - val allSelected = downloadViewModel.isAllSelected() + val allSelected = downloadViewModel.isAllHeadersSelected() if (allSelected) { binding.btnToggleAll.setText(R.string.deselect_all) } else binding.btnToggleAll.setText(R.string.select_all) @@ -251,7 +260,7 @@ class DownloadFragment : BaseFragment( } } - private fun handleSelectedChange(selected: MutableSet) { + private fun handleSelectedChange(selected: Set) { if (selected.isNotEmpty()) { binding?.downloadDeleteAppbar?.isVisible = true binding?.downloadAppbar?.isVisible = false @@ -270,14 +279,11 @@ class DownloadFragment : BaseFragment( } binding?.btnToggleAll?.setOnClickListener { - val allSelected = downloadViewModel.isAllSelected() - val adapter = binding?.downloadList?.adapter as? DownloadAdapter + val allSelected = downloadViewModel.isAllHeadersSelected() if (allSelected) { - adapter?.notifySelectionStates() downloadViewModel.clearSelectedItems() } else { - adapter?.notifyAllSelected() - downloadViewModel.selectAllItems() + downloadViewModel.selectAllHeaders() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt index 137f1355e25..bf81e606987 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt @@ -6,20 +6,22 @@ import android.os.Environment import android.os.StatFs import androidx.appcompat.app.AlertDialog import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.isEpisodeBased +import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus +import com.lagradost.cloudstream3.utils.ConsistentLiveData import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE import com.lagradost.cloudstream3.utils.DataStore.getFolderName import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKeys +import com.lagradost.cloudstream3.utils.ResourceLiveData import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadManager.deleteFilesAndUpdateSettings import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadFileInfoAndUpdateSettings @@ -27,69 +29,80 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext class DownloadViewModel : ViewModel() { + private val _headerCards = ResourceLiveData>(Resource.Loading()) + val headerCards: LiveData>> = _headerCards - private val _headerCards = MutableLiveData>() - val headerCards: LiveData> = _headerCards + private val _childCards = ResourceLiveData>(Resource.Loading()) + val childCards: LiveData>> = _childCards - private val _childCards = MutableLiveData>() - val childCards: LiveData> = _childCards - - private val _usedBytes = MutableLiveData() + private val _usedBytes = ConsistentLiveData() val usedBytes: LiveData = _usedBytes - private val _availableBytes = MutableLiveData() + private val _availableBytes = ConsistentLiveData() val availableBytes: LiveData = _availableBytes - private val _downloadBytes = MutableLiveData() + private val _downloadBytes = ConsistentLiveData() val downloadBytes: LiveData = _downloadBytes - private val _selectedBytes = MutableLiveData(0) + private val _selectedBytes = ConsistentLiveData(0) val selectedBytes: LiveData = _selectedBytes - private val _isMultiDeleteState = MutableLiveData(false) + private val _isMultiDeleteState = ConsistentLiveData(false) val isMultiDeleteState: LiveData = _isMultiDeleteState - private val _selectedItemIds = MutableLiveData>(mutableSetOf()) - val selectedItemIds: LiveData> = _selectedItemIds - - private var previousVisual: List? = null + private val _selectedItemIds = ConsistentLiveData>(mutableSetOf()) + val selectedItemIds: LiveData> = _selectedItemIds fun setIsMultiDeleteState(value: Boolean) { _isMultiDeleteState.postValue(value) } fun addSelected(itemId: Int) { - updateSelectedItems { it.add(itemId) } + updateSelectedItems { it + itemId } } fun removeSelected(itemId: Int) { - updateSelectedItems { it.remove(itemId) } + updateSelectedItems { it - itemId } + } + + fun selectAllHeaders() { + updateSelectedItems { + _headerCards.success.orEmpty() + .map { item -> item.data.id }.toSet() + } } - fun selectAllItems() { - val items = headerCards.value.orEmpty() + childCards.value.orEmpty() - updateSelectedItems { it.addAll(items.map { item -> item.data.id }) } + fun selectAllChildren() { + updateSelectedItems { + _childCards.success.orEmpty() + .map { item -> item.data.id }.toSet() + } } fun clearSelectedItems() { // We need this to be done immediately // so we can't use postValue - _selectedItemIds.value = mutableSetOf() - updateSelectedItems { it.clear() } + updateSelectedItems { emptySet() } + } + + fun isAllChildrenSelected(): Boolean { + val currentSelected = selectedItemIds.value ?: return false + val children = _childCards.success.orEmpty() + return currentSelected.size == children.size && children.all { it.data.id in currentSelected } } - fun isAllSelected(): Boolean { + fun isAllHeadersSelected(): Boolean { val currentSelected = selectedItemIds.value ?: return false - val items = headerCards.value.orEmpty() + childCards.value.orEmpty() - return items.count() == currentSelected.count() && items.all { it.data.id in currentSelected } + val headers = _headerCards.success.orEmpty() + return currentSelected.size == headers.size && headers.all { it.data.id in currentSelected } } - private fun updateSelectedItems(action: (MutableSet) -> Unit) { - val currentSelected = selectedItemIds.value ?: mutableSetOf() - action(currentSelected) + private fun updateSelectedItems(action: (Set) -> Set) { + val currentSelected = action(selectedItemIds.value ?: mutableSetOf()) _selectedItemIds.postValue(currentSelected) + postHeaders() + postChildren() updateSelectedBytes() - updateSelectedCards() } private fun updateSelectedBytes() = viewModelScope.launchSafe { @@ -98,25 +111,12 @@ class DownloadViewModel : ViewModel() { _selectedBytes.postValue(totalSelectedBytes) } - private fun updateSelectedCards() = viewModelScope.launchSafe { - val currentSelected = selectedItemIds.value ?: return@launchSafe - - headerCards.value?.let { headers -> - headers.forEach { header -> - header.isSelected = header.data.id in currentSelected - } - _headerCards.postValue(headers) - } - - childCards.value?.let { children -> - children.forEach { child -> - child.isSelected = child.data.id in currentSelected - } - _childCards.postValue(children) - } - } fun updateHeaderList(context: Context) = viewModelScope.launchSafe { + // Do not push loading as it interrupts the UI + //_headerCards.postValue(Resource.Loading()) + clearSelectedItems() + val visual = withContext(Dispatchers.IO) { val children = context.getKeys(DOWNLOAD_EPISODE_CACHE) .mapNotNull { context.getKey(it) } @@ -133,11 +133,32 @@ class DownloadViewModel : ViewModel() { ) } - if (visual != previousVisual) { - previousVisual = visual - updateStorageStats(visual) - _headerCards.postValue(visual) - } + updateStorageStats(visual) + postHeaders(visual) + } + + fun postHeaders(newValue: List? = null) { + val newValue = newValue ?: _headerCards.success ?: return + val selection = selectedItemIds.value ?: emptySet() + _headerCards.postValue(Resource.Success(newValue.map { + it.copy( + isSelected = selection.contains( + it.data.id + ) + ) + })) + } + + fun postChildren(newValue: List? = null) { + val newValue = newValue ?: _childCards.success ?: return + val selection = selectedItemIds.value ?: emptySet() + _childCards.postValue(Resource.Success(newValue.map { + it.copy( + isSelected = selection.contains( + it.data.id + ) + ) + })) } private fun calculateDownloadStats( @@ -152,7 +173,8 @@ class DownloadViewModel : ViewModel() { val totalDownloads = mutableMapOf() children.forEach { child -> - val childFile = getDownloadFileInfoAndUpdateSettings(context, child.id) ?: return@forEach + val childFile = + getDownloadFileInfoAndUpdateSettings(context, child.id) ?: return@forEach if (childFile.fileLength <= 1) return@forEach val len = childFile.totalBytes @@ -179,10 +201,11 @@ class DownloadViewModel : ViewModel() { if (bytes <= 0 || downloads <= 0) return@mapNotNull null val isSelected = selectedItemIds.value?.contains(it.id) ?: false - val movieEpisode = if (it.type.isEpisodeBased()) null else context.getKey( - DOWNLOAD_EPISODE_CACHE, - getFolderName(it.id.toString(), it.id.toString()) - ) + val movieEpisode = + if (it.type.isEpisodeBased()) null else context.getKey( + DOWNLOAD_EPISODE_CACHE, + getFolderName(it.id.toString(), it.id.toString()) + ) VisualDownloadCached.Header( currentBytes = currentBytes, @@ -208,12 +231,16 @@ class DownloadViewModel : ViewModel() { } fun updateChildList(context: Context, folder: String) = viewModelScope.launchSafe { + _childCards.postValue(Resource.Loading()) // always push loading + clearSelectedItems() + val visual = withContext(Dispatchers.IO) { context.getKeys(folder).mapNotNull { key -> context.getKey(key) }.mapNotNull { val isSelected = selectedItemIds.value?.contains(it.id) ?: false - val info = getDownloadFileInfoAndUpdateSettings(context, it.id) ?: return@mapNotNull null + val info = + getDownloadFileInfoAndUpdateSettings(context, it.id) ?: return@mapNotNull null VisualDownloadCached.Child( currentBytes = info.fileLength, totalBytes = info.totalBytes, @@ -221,24 +248,20 @@ class DownloadViewModel : ViewModel() { data = it, ) } - }.sortedWith(compareBy( - // Sort by season first, and then by episode number, - // to ensure sorting is consistent. - { it.data.season ?: 0 }, - { it.data.episode } - )) - - if (previousVisual != visual) { - previousVisual = visual - _childCards.postValue(visual) - } + }.sortedWith( + compareBy( + // Sort by season first, and then by episode number, + // to ensure sorting is consistent. + { it.data.season ?: 0 }, + { it.data.episode } + )) + + postChildren(visual) } private fun removeItems(idsToRemove: Set) = viewModelScope.launchSafe { - val updatedHeaders = headerCards.value.orEmpty().filter { it.data.id !in idsToRemove } - val updatedChildren = childCards.value.orEmpty().filter { it.data.id !in idsToRemove } - _headerCards.postValue(updatedHeaders) - _childCards.postValue(updatedChildren) + postHeaders(_headerCards.success?.filter { it.data.id !in idsToRemove }) + postChildren(_childCards.success?.filter { it.data.id !in idsToRemove }) } private fun updateStorageStats(visual: List) { @@ -414,8 +437,8 @@ class DownloadViewModel : ViewModel() { } private fun getSelectedItemsData(): List? { - val headers = headerCards.value.orEmpty() - val children = childCards.value.orEmpty() + val headers = _headerCards.success.orEmpty() + val children = _childCards.success.orEmpty() return selectedItemIds.value?.mapNotNull { id -> headers.find { it.data.id == id } ?: children.find { it.data.id == id } @@ -423,10 +446,11 @@ class DownloadViewModel : ViewModel() { } private fun getItemDataFromId(itemId: Int): List { - val headers = headerCards.value.orEmpty() - val children = childCards.value.orEmpty() + return (_headerCards.success.orEmpty() + _childCards.success.orEmpty()).filter { it.data.id == itemId } + } - return (headers + children).filter { it.data.id == itemId } + fun clearChildren() { + _childCards.postValue(Resource.Loading()) } private data class DeleteData( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 6ad0fffc6be..c2d5e43e954 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -16,6 +16,7 @@ import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.MainSettingsBinding import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthRepo import com.lagradost.cloudstream3.ui.BaseFragment @@ -136,7 +137,7 @@ class SettingsFragment : BaseFragment( setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) children.firstOrNull { it is ImageView }?.tag = getString(R.string.tv_no_focus_tag) setNavigationOnClickListener { - activity?.onBackPressedDispatcher?.onBackPressed() + safe { activity?.onBackPressedDispatcher?.onBackPressed() } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt index 15213111122..ee333abada5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt @@ -66,7 +66,7 @@ class PluginsFragment : BaseFragment( val downloadAllButton = binding.settingsToolbar.menu?.findItem(R.id.download_all) if (url == null || name == null) { - activity?.onBackPressedDispatcher?.onBackPressed() + dispatchBackPressed() return } @@ -126,7 +126,7 @@ class PluginsFragment : BaseFragment( if (searchView?.isIconified == false) { searchView.isIconified = true } else { - activity?.onBackPressedDispatcher?.onBackPressed() + dispatchBackPressed() } } searchView?.setOnQueryTextFocusChangeListener { _, hasFocus -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt new file mode 100644 index 00000000000..7bb777bc475 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt @@ -0,0 +1,44 @@ +package com.lagradost.cloudstream3.utils + +import androidx.annotation.MainThread +import androidx.lifecycle.LiveData +import com.lagradost.cloudstream3.mvvm.Resource + +/** + * This is an atomic LiveData where you can do .value instantly after doing .postValue + * + * Fuck all that is LiveData, because we want this value to be accessible everywhere instantly. + * */ +open class ConsistentLiveData(initValue : T? = null) : LiveData() { + @Volatile private var internalValue : T? = initValue + + override fun getValue(): T? { + return internalValue + } + + /** If someone want the old behavior then good for them */ + val postedValue : T? get() = super.getValue() + + public override fun postValue(value : T?) { + super.postValue(value) + internalValue = value + } + + @MainThread + public override fun setValue(value: T?) { + super.setValue(value) + internalValue = value + } +} + +/** Atomic resource livedata, to make it easier to work with resources without local copies */ +class ResourceLiveData(initValue : Resource? = null) : ConsistentLiveData>(initValue) { + var success + get() = when(val output = this.value) { + is Resource.Success -> { + output.value + } + else -> null + } + set(value) = this.postValue(value?.let { Resource.Success(it) } ) +} From 1aa6a6215df2dc994e3a3f851d2fbba775749cbf Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:37:55 +0100 Subject: [PATCH 336/618] Minor fix to ConsistentLiveData --- .../com/lagradost/cloudstream3/utils/ConsistentLiveData.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt index 7bb777bc475..def41d7a07a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt @@ -5,11 +5,14 @@ import androidx.lifecycle.LiveData import com.lagradost.cloudstream3.mvvm.Resource /** - * This is an atomic LiveData where you can do .value instantly after doing .postValue + * This is an atomic LiveData where you can do .value instantly after doing .postValue. + * + * The default behavior is a footgun that will cause race conditions, + * as we do not really care if it is posted as we only want the latest data (even in the binding). * * Fuck all that is LiveData, because we want this value to be accessible everywhere instantly. * */ -open class ConsistentLiveData(initValue : T? = null) : LiveData() { +open class ConsistentLiveData(initValue : T? = null) : LiveData(initValue) { @Volatile private var internalValue : T? = initValue override fun getValue(): T? { From b68fadc956b789e5d1b26c8649f784537de29ca9 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Sun, 30 Nov 2025 00:24:08 +0100 Subject: [PATCH 337/618] Minor fixes to recycled DownloadAdapter cards --- .../com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt | 4 ++++ .../cloudstream3/ui/download/button/BaseFetchButton.kt | 1 + 2 files changed, 5 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt index b26e99d81b2..d0740f66a81 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt @@ -199,6 +199,7 @@ class DownloadAdapter( ) } + downloadHeaderInfo.isVisible = true downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, onItemClickEvent) downloadButton.isVisible = !isMultiDeleteState @@ -218,11 +219,14 @@ class DownloadAdapter( card: VisualDownloadCached.Header, formattedSize: String ) { + downloadButton.resetViewData() + watchProgressContainer.isVisible = false downloadButton.isVisible = false downloadHeaderEpisodeProgress.isVisible = false downloadHeaderGotoChild.isVisible = !isMultiDeleteState try { + downloadHeaderInfo.isVisible = true downloadHeaderInfo.text = downloadHeaderInfo.context.getString(R.string.extra_info_format).format( card.totalDownloads, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt index 908e3a80abc..36a84d9f311 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt @@ -62,6 +62,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : open fun resetViewData() { // lastRequest = null + progressText = null isZeroBytes = true doSetProgress = true persistentId = null From d794f6182efe9cecf1118bc1ec51d85fa2bc3783 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:11:05 -0700 Subject: [PATCH 338/618] Add Prerelease annotation to extractors that are not in stable (#2281) --- .../com/lagradost/cloudstream3/extractors/HubCloud.kt | 2 ++ .../com/lagradost/cloudstream3/extractors/OkRuExtractor.kt | 4 ++++ .../cloudstream3/extractors/PixelDrainExtractor.kt | 6 +++--- .../com/lagradost/cloudstream3/extractors/VkExtractor.kt | 2 ++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt index f8b289469fb..d8a3fb1ec00 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.extractors import com.lagradost.api.Log +import com.lagradost.cloudstream3.Prerelease import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app @@ -11,6 +12,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor import com.lagradost.cloudstream3.utils.newExtractorLink import java.net.URI +@Prerelease class HubCloud : ExtractorApi() { override val name = "Hub-Cloud" override val mainUrl = "https://hubcloud.*" diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt index d9803fa3ef8..f5f258cfe42 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt @@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.extractors +import com.lagradost.cloudstream3.Prerelease + open class OkRuSSL : Odnoklassniki() { override var name = "OkRuSSL" override var mainUrl = "https://ok.ru" @@ -12,10 +14,12 @@ open class OkRuHTTP : Odnoklassniki() { override var mainUrl = "http://ok.ru" } +@Prerelease class OkRuSSLMobile : OkRuSSL() { override var mainUrl = "https://m.ok.ru" } +@Prerelease class OkRuHTTPMobile : OkRuHTTP() { override var mainUrl = "http://m.ok.ru" } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PixelDrainExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PixelDrainExtractor.kt index 4651f769a64..3426289f8b6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PixelDrainExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PixelDrainExtractor.kt @@ -5,9 +5,11 @@ package com.lagradost.cloudstream3.extractors import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* -class PixelDrainDev : PixelDrain(){ +@Prerelease +class PixelDrainDev : PixelDrain() { override var mainUrl = "https://pixeldrain.dev" } + open class PixelDrain : ExtractorApi() { override val name = "PixelDrain" override val mainUrl = "https://pixeldrain.com" @@ -40,5 +42,3 @@ open class PixelDrain : ExtractorApi() { } } } - - diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VkExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VkExtractor.kt index 8e454087436..5009cea3e67 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VkExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VkExtractor.kt @@ -1,6 +1,7 @@ // Made by @kraptor123 for cs-kraptor package com.lagradost.cloudstream3.extractors +import com.lagradost.cloudstream3.Prerelease import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi @@ -9,6 +10,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink +@Prerelease open class VkExtractor : ExtractorApi() { override val name = "Vk" override val mainUrl = "https://vkvideo.ru" From dad6b92ae3bdc34026a322104b3801a280c76adb Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:15:11 -0700 Subject: [PATCH 339/618] Fix downloads loading background (#2279) --- .../main/res/layout/fragment_downloads.xml | 106 +++++++++--------- 1 file changed, 50 insertions(+), 56 deletions(-) diff --git a/app/src/main/res/layout/fragment_downloads.xml b/app/src/main/res/layout/fragment_downloads.xml index 48e8bb07454..d6f41d6b0b8 100644 --- a/app/src/main/res/layout/fragment_downloads.xml +++ b/app/src/main/res/layout/fragment_downloads.xml @@ -266,68 +266,62 @@ - - - - - - - + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + + + + + + + + + + + + + + + - - - - - - - - - + android:layout_gravity="center" + android:layout_marginBottom="30dp" + android:text="@string/downloads_empty" + android:gravity="center" + android:visibility="gone" + tools:visibility="visible" /> + Date: Sun, 30 Nov 2025 12:19:52 -0700 Subject: [PATCH 340/618] Fix no poster showing wrong poster (#2278) --- .../ui/search/SearchResultBuilder.kt | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt index 4c0aeb1bb2c..93526b57e45 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt @@ -128,18 +128,11 @@ object SearchResultBuilder { cardText?.text = card.name cardText?.isVisible = showTitle cardView.isVisible = true - cardView.loadImage(card.posterUrl, card.posterHeaders) { - error { getImageFromDrawable(itemView.context, R.drawable.default_cover) } - /* - createPaletteAsync is currently disabled as we use hardware acceleration on images - val posterUrl = card.posterUrl - if (posterUrl != null && colorCallback != null) { - this.listener(onSuccess = { _,success -> - val bitmap = success.image.toBitmap() - createPaletteAsync(posterUrl, bitmap, colorCallback) - }) - }*/ - } + if (!card.posterUrl.isNullOrEmpty()) { + cardView.loadImage(card.posterUrl, card.posterHeaders) { + error { getImageFromDrawable(itemView.context, R.drawable.default_cover) } + } + } else cardView.loadImage(R.drawable.default_cover) fun click(view: View?) { clickCallback.invoke( @@ -330,4 +323,4 @@ object SearchResultBuilder { backgroundTintList = ColorStateList.valueOf(context.colorFromAttribute(R.attr.textColor)) } } -} \ No newline at end of file +} From 1dd477a9658e4730f3e67339559ac953612b944e Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:21:09 -0700 Subject: [PATCH 341/618] Disable MissingTranslation lint (#2276) Translations are handled by weblate, so we don't really care about missing translations here. --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c0e55071baa..e2bdb20792e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -150,6 +150,7 @@ android { lint { abortOnError = false checkReleaseBuilds = false + disable.add("MissingTranslation") } buildFeatures { From 2ac0698bd23dda0d8f3ef8cbee488ce24b6c8cff Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:32:20 -0700 Subject: [PATCH 342/618] Handle new Android 16 biometrics error type (#2275) Adds handling for `BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS` which was added in API level 36. --- .../utils/BiometricAuthenticator.kt | 65 ++++++++++++------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt index 1d9cf5f46ee..bce8f09dced 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.utils +import android.annotation.SuppressLint import android.app.Activity import android.app.KeyguardManager import android.content.Context @@ -100,31 +101,51 @@ object BiometricAuthenticator { } private fun isBiometricHardWareAvailable(): Boolean { - // authentication occurs only when this is true and device is truly capable + // Authentication occurs only when this is true and device is truly capable. var result = false + when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA -> { + @SuppressLint("RestrictedApi") + when (biometricManager?.canAuthenticate( + DEVICE_CREDENTIAL or BIOMETRIC_STRONG or BIOMETRIC_WEAK + )) { + BiometricManager.BIOMETRIC_SUCCESS -> result = true + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false + BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS -> result = false + BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true + BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true + BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false + } + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - when (biometricManager?.canAuthenticate( - DEVICE_CREDENTIAL or BIOMETRIC_STRONG or BIOMETRIC_WEAK - )) { - BiometricManager.BIOMETRIC_SUCCESS -> result = true - BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false - BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false - BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false - BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true - BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true - BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { + @Suppress("SwitchIntDef") + when (biometricManager?.canAuthenticate( + DEVICE_CREDENTIAL or BIOMETRIC_STRONG or BIOMETRIC_WEAK + )) { + BiometricManager.BIOMETRIC_SUCCESS -> result = true + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false + BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true + BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true + BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false + } } - } else { - @Suppress("DEPRECATION") - when (biometricManager?.canAuthenticate()) { - BiometricManager.BIOMETRIC_SUCCESS -> result = true - BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false - BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false - BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false - BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true - BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true - BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false + + else -> { + @Suppress("DEPRECATION", "SwitchIntDef") + when (biometricManager?.canAuthenticate()) { + BiometricManager.BIOMETRIC_SUCCESS -> result = true + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false + BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true + BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true + BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false + } } } From 81b2718129639efe6d08750d502a53e43a5788a1 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Fri, 5 Dec 2025 05:04:52 +0530 Subject: [PATCH 343/618] horizontal poster in expanded list (#2286) --- .../com/lagradost/cloudstream3/ui/home/HomeFragment.kt | 10 +++++++--- .../lagradost/cloudstream3/ui/search/SearchAdaptor.kt | 5 ++++- .../java/com/lagradost/cloudstream3/utils/UIHelper.kt | 8 ++++---- app/src/main/res/layout/search_result_grid.xml | 2 +- .../main/res/layout/search_result_grid_expanded.xml | 2 +- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 30b6b29d922..bc085df7e7d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -195,10 +195,10 @@ class HomeFragment : BaseFragment( // Span settings - binding.homeExpandedRecycler.spanCount = currentSpan + binding.homeExpandedRecycler.spanCount = context.getSpanCount(item.isHorizontalImages) binding.homeExpandedRecycler.setRecycledViewPool(SearchAdapter.sharedPool) binding.homeExpandedRecycler.adapter = - SearchAdapter(binding.homeExpandedRecycler) { callback -> + SearchAdapter(binding.homeExpandedRecycler,item.isHorizontalImages) { callback -> handleSearchClickCallback(callback) if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) { bottomSheetDialogBuilder.ownHide() // we hide here because we want to resume it later @@ -238,7 +238,11 @@ class HomeFragment : BaseFragment( }) val spanListener = { span: Int -> - binding.homeExpandedRecycler.spanCount = span + if(item.isHorizontalImages){ + binding.homeExpandedRecycler.spanCount = context.getSpanCount(true) + }else{ + binding.homeExpandedRecycler.spanCount = span + } //(recycle.adapter as SearchAdapter).notifyDataSetChanged() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index 7c763bf4219..9338d494249 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -32,6 +32,7 @@ class SearchClickCallback( class SearchAdapter( private val resView: AutofitRecyclerView, + private val isHorizontal:Boolean = false, private val clickCallback: (SearchClickCallback) -> Unit, ) : NoStateAdapter(diffCallback = BaseDiffCallback(itemSame = { a, b -> if (a.id != null || b.id != null) { @@ -47,7 +48,9 @@ class SearchAdapter( var hasNext: Boolean = false - private val coverHeight: Int get() = (resView.itemWidth / 0.68).roundToInt() + private val coverRatio = if(isHorizontal) 1.8 else 0.68 + + private val coverHeight: Int get() = (resView.itemWidth / coverRatio).roundToInt() override fun onCreateContent(parent: ViewGroup): ViewHolderState { val inflater = LayoutInflater.from(parent.context) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index e114abe2942..6a8dabadafc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -200,10 +200,10 @@ object UIHelper { listView.requestLayout() } - fun Context.getSpanCount(): Int { - val compactView = false - val spanCountLandscape = if (compactView) 2 else 6 - val spanCountPortrait = if (compactView) 1 else 3 + fun Context.getSpanCount(isHorizontal:Boolean=false): Int { +// val compactView = false + val spanCountLandscape = if (isHorizontal) 3 else 6 + val spanCountPortrait = if (isHorizontal) 2 else 3 val orientation = resources.configuration.orientation return if (orientation == Configuration.ORIENTATION_LANDSCAPE) { diff --git a/app/src/main/res/layout/search_result_grid.xml b/app/src/main/res/layout/search_result_grid.xml index 601860b4822..02fdf0211b9 100644 --- a/app/src/main/res/layout/search_result_grid.xml +++ b/app/src/main/res/layout/search_result_grid.xml @@ -14,7 +14,7 @@ Date: Thu, 4 Dec 2025 23:50:56 +0000 Subject: [PATCH 344/618] Fix: Configuration change view invalidation on AutofitRecyclerView popup --- .../cloudstream3/ui/home/HomeFragment.kt | 29 +++++++++---------- .../ui/quicksearch/QuickSearchFragment.kt | 2 +- .../cloudstream3/ui/search/SearchFragment.kt | 2 +- .../com/lagradost/cloudstream3/utils/Event.kt | 25 ++++++++++++++++ 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index bc085df7e7d..6c58fac9a8b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -16,7 +16,9 @@ import android.widget.ListView import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.net.toUri import androidx.core.view.isGone +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager @@ -64,7 +66,7 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.ownShow import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.Event +import com.lagradost.cloudstream3.utils.EmptyEvent import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.TvChannelUtils import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe @@ -73,17 +75,15 @@ import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.toPx -import com.lagradost.cloudstream3.utils.txt -import androidx.core.net.toUri -import androidx.core.view.isInvisible private const val TAG = "HomeFragment" class HomeFragment : BaseFragment( - BaseFragment.BindingCreator.Bind(FragmentHomeBinding::bind) + BindingCreator.Bind(FragmentHomeBinding::bind) ) { companion object { - val configEvent = Event() + // Used for configuration changed events to fix any popups that are not attached to a fragment + val configEvent = EmptyEvent() var currentSpan = 1 val listHomepageItems = mutableListOf() @@ -114,6 +114,7 @@ class HomeFragment : BaseFragment( //} // returns a BottomSheetDialog that will be hidden with OwnHidden upon hide, and must be saved to be able call ownShow in onCreateView + fun Activity.loadHomepageList( expand: HomeViewModel.ExpandableHomepageList, deleteCallback: (() -> Unit)? = null, @@ -237,13 +238,12 @@ class HomeFragment : BaseFragment( } }) - val spanListener = { span: Int -> - if(item.isHorizontalImages){ - binding.homeExpandedRecycler.spanCount = context.getSpanCount(true) - }else{ - binding.homeExpandedRecycler.spanCount = span - } - //(recycle.adapter as SearchAdapter).notifyDataSetChanged() + val spanListener = Runnable { + binding.homeExpandedRecycler.spanCount = context.getSpanCount(item.isHorizontalImages) + // We want to rebind everything to update the UI, however we also want to avoid + // any animations ect, this is the easiest way to do this, and the most correct + @SuppressLint("NotifyDataSetChanged") + binding.homeExpandedRecycler.adapter?.notifyDataSetChanged() } configEvent += spanListener @@ -621,8 +621,7 @@ class HomeFragment : BaseFragment( ) // Fix grid - currentSpan = view.context.getSpanCount() - configEvent.invoke(currentSpan) + configEvent.invoke() } @SuppressLint("SetTextI18n") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt index a716cab4151..724276ab763 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt @@ -98,7 +98,7 @@ class QuickSearchFragment : BaseFragment( // Fix grid HomeFragment.currentSpan = view.context.getSpanCount() binding?.quickSearchAutofitResults?.spanCount = HomeFragment.currentSpan - HomeFragment.configEvent.invoke(HomeFragment.currentSpan) + HomeFragment.configEvent.invoke() } override fun onCreateView( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index d1efe62054b..ae31d03fbb1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -220,7 +220,7 @@ class SearchFragment : BaseFragment( // Fix grid currentSpan = view.context.getSpanCount() binding?.searchAutofitResults?.spanCount = currentSpan - HomeFragment.configEvent.invoke(currentSpan) + HomeFragment.configEvent.invoke() } override fun onBindingCreated( diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/Event.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/Event.kt index a0dfe734e54..f66da4e5ff3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/Event.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/Event.kt @@ -24,3 +24,28 @@ class Event { } } } + +class EmptyEvent { + private val observers = mutableSetOf() + + val size: Int get() = observers.size + + operator fun plusAssign(observer: Runnable) { + synchronized(observers) { + observers.add(observer) + } + } + + operator fun minusAssign(observer: Runnable) { + synchronized(observers) { + observers.remove(observer) + } + } + + operator fun invoke() { + synchronized(observers) { + for (observer in observers) + observer.run() + } + } +} From 0b3aa24e66fce2b1428502fc1189f012c764c21d Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:09:54 -0700 Subject: [PATCH 345/618] Some cleanup/improvements to layouts (#2274) --- .../player_select_source_and_subs.xml | 1 + .../player_select_source_priority.xml | 5 +- app/src/main/res/layout/add_account_input.xml | 1 + app/src/main/res/layout/add_repo_input.xml | 1 + app/src/main/res/layout/add_site_input.xml | 1 + .../main/res/layout/bottom_input_dialog.xml | 71 ++++++++++--------- .../layout/chromecast_subtitle_settings.xml | 2 +- .../custom_preference_category_material.xml | 3 - .../res/layout/custom_preference_material.xml | 5 -- .../custom_preference_widget_seekbar.xml | 15 ++-- app/src/main/res/layout/empty_layout.xml | 33 ++++----- .../main/res/layout/fragment_extensions.xml | 4 +- app/src/main/res/layout/fragment_player.xml | 1 - .../main/res/layout/fragment_player_tv.xml | 1 - .../res/layout/fragment_plugin_details.xml | 2 +- .../main/res/layout/fragment_result_tv.xml | 2 +- app/src/main/res/layout/fragment_trailer.xml | 1 - app/src/main/res/layout/lock_pin_dialog.xml | 1 + app/src/main/res/layout/options_popup_tv.xml | 4 +- .../main/res/layout/player_custom_layout.xml | 2 +- .../res/layout/player_custom_layout_tv.xml | 1 - .../layout/player_select_source_and_subs.xml | 1 + .../layout/player_select_source_priority.xml | 3 +- app/src/main/res/layout/result_episode.xml | 3 +- app/src/main/res/layout/result_sync.xml | 1 + app/src/main/res/layout/stream_input.xml | 2 + app/src/main/res/layout/subtitle_offset.xml | 1 + app/src/main/res/layout/subtitle_settings.xml | 2 +- .../main/res/layout/trailer_custom_layout.xml | 1 - 29 files changed, 83 insertions(+), 88 deletions(-) diff --git a/app/src/main/res/layout-port/player_select_source_and_subs.xml b/app/src/main/res/layout-port/player_select_source_and_subs.xml index 06aed87fceb..4710473d4fa 100644 --- a/app/src/main/res/layout-port/player_select_source_and_subs.xml +++ b/app/src/main/res/layout-port/player_select_source_and_subs.xml @@ -120,6 +120,7 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" + android:baselineAligned="false" android:orientation="horizontal"> + tools:text="@string/profile_number" + tools:ignore="LabelFor" /> + xmlns:tools="http://schemas.android.com/tools" + android:nextFocusDown="@id/nginx_text_input" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:id="@+id/nginx_text_input" + android:nextFocusRight="@id/cancel_btt" + android:nextFocusLeft="@id/apply_btt" + android:layout_marginBottom="60dp" + android:layout_marginHorizontal="10dp" + android:paddingTop="10dp" + android:requiresFadingEdge="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_rowWeight="1" + android:autofillHints="no" + android:inputType="text" + tools:text="nginx.com" + tools:ignore="LabelFor" /> + android:id="@+id/apply_btt_holder" + android:orientation="horizontal" + android:layout_gravity="bottom" + android:gravity="bottom|end" + android:layout_marginTop="-60dp" + android:layout_width="match_parent" + android:layout_height="60dp"> + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|end" + android:text="@string/sort_apply" + android:id="@+id/apply_btt" + style="@style/WhiteButton" /> + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|end" + android:text="@string/sort_cancel" + android:id="@+id/cancel_btt" + style="@style/BlackButton" /> diff --git a/app/src/main/res/layout/chromecast_subtitle_settings.xml b/app/src/main/res/layout/chromecast_subtitle_settings.xml index b1073190c21..92d0bd35023 100644 --- a/app/src/main/res/layout/chromecast_subtitle_settings.xml +++ b/app/src/main/res/layout/chromecast_subtitle_settings.xml @@ -12,7 +12,7 @@ diff --git a/app/src/main/res/layout/custom_preference_widget_seekbar.xml b/app/src/main/res/layout/custom_preference_widget_seekbar.xml index 02c5ec1be36..132091e5f09 100644 --- a/app/src/main/res/layout/custom_preference_widget_seekbar.xml +++ b/app/src/main/res/layout/custom_preference_widget_seekbar.xml @@ -18,13 +18,12 @@ + android:ellipsize="marquee" + tools:ignore="LabelFor" /> @@ -99,9 +96,7 @@ android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" - android:paddingLeft="@dimen/preference_seekbar_padding_horizontal" android:paddingStart="@dimen/preference_seekbar_padding_horizontal" - android:paddingRight="@dimen/preference_seekbar_padding_horizontal" android:paddingEnd="@dimen/preference_seekbar_padding_horizontal" android:paddingTop="@dimen/preference_seekbar_padding_vertical" android:paddingBottom="@dimen/preference_seekbar_padding_vertical" @@ -113,13 +108,11 @@ - + - - \ No newline at end of file + + diff --git a/app/src/main/res/layout/fragment_extensions.xml b/app/src/main/res/layout/fragment_extensions.xml index fd1d0dade55..b7cf4b6cd34 100644 --- a/app/src/main/res/layout/fragment_extensions.xml +++ b/app/src/main/res/layout/fragment_extensions.xml @@ -70,8 +70,8 @@ android:nextFocusUp="@id/repo_recycler_view" android:orientation="horizontal" android:padding="10dp" - - android:visibility="visible"> + android:visibility="visible" + android:baselineAligned="false"> diff --git a/app/src/main/res/layout/result_episode.xml b/app/src/main/res/layout/result_episode.xml index 361ec02316a..19f6688899b 100644 --- a/app/src/main/res/layout/result_episode.xml +++ b/app/src/main/res/layout/result_episode.xml @@ -54,7 +54,8 @@ + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:orientation="horizontal"> @@ -38,6 +39,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="20" + android:autofillHints="no" android:hint="@string/referer" android:inputType="textUri" tools:ignore="LabelFor" /> diff --git a/app/src/main/res/layout/subtitle_offset.xml b/app/src/main/res/layout/subtitle_offset.xml index 6741de807cb..8570e9a266b 100644 --- a/app/src/main/res/layout/subtitle_offset.xml +++ b/app/src/main/res/layout/subtitle_offset.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:baselineAligned="false" android:orientation="horizontal"> From 2c0fa701016da996f2a762942a7c42b02148c947 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:12:14 -0700 Subject: [PATCH 346/618] Clear home page adapter pools when reloading (#2272) --- .../java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index b7a322a8482..6df5bbbef1d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -501,6 +501,9 @@ class HomeViewModel : ViewModel() { return@ioSafe } + HomeChildItemAdapter.sharedPool.clear() + ParentItemAdapter.sharedPool.clear() + val api = getApiFromNameNull(preferredApiName) if (preferredApiName == noneApi.name) { // just set to random From b2e06c5966ec6f40d85291b378d73aa8e2629486 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:13:38 -0700 Subject: [PATCH 347/618] Remove `BuildConfig.BETA` (#2290) It's unused and can be accessed with `BuildConfig.FLAVOR == "prerelease"` --- app/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e2bdb20792e..c386aca897a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -121,7 +121,6 @@ android { create("prerelease") { dimension = "state" resValue("bool", "is_prerelease", "true") - buildConfigField("boolean", "BETA", "true") applicationIdSuffix = ".prerelease" if (signingConfigs.names.contains("prerelease")) { signingConfig = signingConfigs.getByName("prerelease") From cd69597a54290e135972a673c3036a534e474515 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:20:08 -0700 Subject: [PATCH 348/618] Move app version to BuildConfig (#2291) Also, the intent seems to be to be to set the version to `-PRE` when in pre release, which doesn't currently work, but this fixes that. --- app/build.gradle.kts | 11 +++++++++- .../ui/settings/SettingsFragment.kt | 3 ++- .../ui/settings/SettingsUpdates.kt | 22 +++++++++++-------- app/src/main/res/layout/main_settings.xml | 6 ++--- app/src/main/res/xml/settings_updates.xml | 3 +-- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c386aca897a..3a1bdc5f75d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -65,7 +65,6 @@ android { versionCode = 67 versionName = "4.6.1" - resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "commit_hash", getGitCommitHash()) resValue("bool", "is_prerelease", "false") @@ -79,6 +78,11 @@ android { "BUILD_DATE", "${System.currentTimeMillis()}" ) + buildConfigField( + "String", + "APP_VERSION", + "\"$versionName\"" + ) buildConfigField( "String", "SIMKL_CLIENT_ID", @@ -128,6 +132,11 @@ android { logger.warn("No prerelease signing config!") } versionNameSuffix = "-PRE" + buildConfigField( + "String", + "APP_VERSION", + "\"${defaultConfig.versionName}$versionNameSuffix\"" + ) versionCode = (System.currentTimeMillis() / 60000).toInt() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index c2d5e43e954..75562002747 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -246,13 +246,14 @@ class SettingsFragment : BaseFragment( } } - val appVersion = getString(R.string.app_version) + val appVersion = BuildConfig.APP_VERSION val commitInfo = getString(R.string.commit_hash) val buildTimestamp = SimpleDateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.getDefault() ).apply { timeZone = TimeZone.getTimeZone("UTC") }.format(Date(BuildConfig.BUILD_DATE)).replace("UTC", "") + binding.appVersion.text = appVersion binding.buildDate.text = buildTimestamp binding.appVersionInfo.setOnLongClickListener { clipboardHelper(txt(R.string.extension_version), "$appVersion $commitInfo $buildTimestamp") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 30cd00470ab..0d34cb9889e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -9,6 +9,7 @@ import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager import com.lagradost.cloudstream3.AutoDownloadMode +import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R @@ -221,18 +222,21 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - getPref(R.string.manual_check_update_key)?.setOnPreferenceClickListener { - ioSafe { - if (activity?.runAutoUpdate(false) == false) { - activity?.runOnUiThread { - showToast( - R.string.no_update_found, - Toast.LENGTH_SHORT - ) + getPref(R.string.manual_check_update_key)?.let { pref -> + pref.summary = BuildConfig.APP_VERSION + pref.setOnPreferenceClickListener { + ioSafe { + if (activity?.runAutoUpdate(false) == false) { + activity?.runOnUiThread { + showToast( + R.string.no_update_found, + Toast.LENGTH_SHORT + ) + } } } + return@setOnPreferenceClickListener true } - return@setOnPreferenceClickListener true } getPref(R.string.auto_download_plugins_key)?.setOnPreferenceClickListener { diff --git a/app/src/main/res/layout/main_settings.xml b/app/src/main/res/layout/main_settings.xml index 0b931843dd1..4a41759e041 100644 --- a/app/src/main/res/layout/main_settings.xml +++ b/app/src/main/res/layout/main_settings.xml @@ -115,12 +115,12 @@ android:orientation="horizontal"> + android:textColor="?attr/textColor" + tools:text="0.0.0" /> + app:key="@string/manual_check_update_key" /> Date: Thu, 4 Dec 2025 17:25:18 -0700 Subject: [PATCH 349/618] Bump material (#2241) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e22a64b2f9..4c507ef2b76 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ juniversalchardet = "2.5.0" kotlinGradlePlugin = "2.2.21" kotlinxCoroutinesCore = "1.10.2" lifecycleKtx = "2.9.4" -material = "1.14.0-alpha06" +material = "1.14.0-alpha07" media3 = "1.8.0" navigationKtx = "2.9.6" newpipeextractor = "v0.24.8" From 93255dfc22b03db2cbfba23d8a22f3eb5e5bc3fb Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:33:30 -0700 Subject: [PATCH 350/618] Add explicit dependency on fragment (#2233) As with some of my other PRs, explicit dependencies allow for better version control. --- app/build.gradle.kts | 1 + gradle/libs.versions.toml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3a1bdc5f75d..d16730d4b32 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -182,6 +182,7 @@ dependencies { implementation(libs.core.ktx) implementation(libs.activity.ktx) implementation(libs.appcompat) + implementation(libs.fragment.ktx) implementation(libs.bundles.lifecycle) implementation(libs.bundles.navigation) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4c507ef2b76..ebbc7a2f52f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ coreKtx = "1.17.0" desugar_jdk_libs_nio = "2.1.5" dokkaGradlePlugin = "2.1.0" espressoCore = "3.7.0" +fragmentKtx = "1.8.9" fuzzywuzzy = "1.4.0" jacksonModuleKotlin = { strictly = "2.13.1" } # Later versions don't support minSdk <26 (Crashes on Android TV's and FireSticks) json = "20250517" @@ -67,6 +68,7 @@ databinding = { module = "androidx.databinding:viewbinding", version.ref = "andr desugar_jdk_libs_nio = { module = "com.android.tools:desugar_jdk_libs_nio", version.ref = "desugar_jdk_libs_nio" } espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } ext-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" } +fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" } fuzzywuzzy = { module = "me.xdrop:fuzzywuzzy", version.ref = "fuzzywuzzy" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jacksonModuleKotlin" } json = { module = "org.json:json", version.ref = "json" } From 472d0bab8b7b22a5bdbadb2e0d478781517a56fd Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:48:29 -0700 Subject: [PATCH 351/618] Remove unused swiperefreshlayout dependency (#2296) --- app/build.gradle.kts | 1 - gradle/libs.versions.toml | 2 -- 2 files changed, 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d16730d4b32..4e58c7ea03d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -190,7 +190,6 @@ dependencies { implementation(libs.preference.ktx) implementation(libs.material) implementation(libs.constraintlayout) - implementation(libs.swiperefreshlayout) // Coil Image Loading implementation(libs.bundles.coil) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ebbc7a2f52f..2f8556cf4dc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,7 +40,6 @@ qrcodeKotlin = "4.5.0" rhino = "1.8.0" safefile = "0.0.8" shimmer = "0.5.0" -swiperefreshlayout = "1.1.0" tmdbJava = "2.13.0" torrentserver = "7861970e038b35cd8c6918384e49caf26903e09e" tvprovider = "1.1.0" @@ -105,7 +104,6 @@ quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" } rhino = { module = "org.mozilla:rhino", version.ref = "rhino" } safefile = { module = "com.github.LagradOst:SafeFile", version.ref = "safefile" } shimmer = { module = "com.facebook.shimmer:shimmer", version.ref = "shimmer" } -swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" } tmdb-java = { module = "com.uwetrottmann.tmdb2:tmdb-java", version.ref = "tmdbJava" } torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref = "torrentserver" } tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } From f2a008922d449052a3a6ec84e93089ab46d694e7 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:51:12 -0700 Subject: [PATCH 352/618] Bump rhino to 1.8.1 (#2295) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2f8556cf4dc..1015a89c5e6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ paletteKtx = "1.0.0" preferenceKtx = "1.2.1" previewseekbarMedia3 = "1.1.1.0" qrcodeKotlin = "4.5.0" -rhino = "1.8.0" +rhino = "1.8.1" safefile = "0.0.8" shimmer = "0.5.0" tmdbJava = "2.13.0" From f77df2f3bfc397e0ddac903042243dca973b76a7 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:52:24 -0700 Subject: [PATCH 353/618] Use temurin distribution for setup-java action (#2297) Per the note on the README for `actions/setup-java`: "AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore. It is highly recommended to migrate workflows from `adopt` and `adopt-openj9`, to `temurin` and `semeru` respectively, to keep receiving software and security updates." --- .github/workflows/build_to_archive.yml | 2 +- .github/workflows/generate_dokka.yml | 2 +- .github/workflows/prerelease.yml | 2 +- .github/workflows/pull_request.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_to_archive.yml b/.github/workflows/build_to_archive.yml index ef7acc9df40..ce920002eb5 100644 --- a/.github/workflows/build_to_archive.yml +++ b/.github/workflows/build_to_archive.yml @@ -38,8 +38,8 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v5 with: + distribution: temurin java-version: 17 - distribution: adopt cache: gradle - name: Grant execute permission for gradlew diff --git a/.github/workflows/generate_dokka.yml b/.github/workflows/generate_dokka.yml index e3490730bb3..e082b79f4bd 100644 --- a/.github/workflows/generate_dokka.yml +++ b/.github/workflows/generate_dokka.yml @@ -43,8 +43,8 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v5 with: + distribution: temurin java-version: 17 - distribution: adopt cache: gradle - name: Set up Android SDK diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 164a8458e95..cee9538bd85 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -29,8 +29,8 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v5 with: + distribution: temurin java-version: 17 - distribution: adopt cache: gradle - name: Grant execute permission for gradlew diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 023647d2a82..0eb3636820c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -11,8 +11,8 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v5 with: + distribution: temurin java-version: 17 - distribution: adopt cache: gradle - name: Grant execute permission for gradlew From fdad31c10ef4470c538cd977c889d43590002130 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 6 Dec 2025 08:29:57 -0700 Subject: [PATCH 354/618] Add backward compatibility for one more AcraApplication method (#2302) `removeKeys()` only seems to be used by one single extension, but I suppose it doesn't hurt to still add back compat for it. --- .../com/lagradost/cloudstream3/AcraApplication.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index 262f5752230..80f084b08f0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3 import android.content.Context import com.lagradost.api.setContext import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.removeKeys import com.lagradost.cloudstream3.utils.DataStore.setKey import java.lang.ref.WeakReference @@ -11,8 +12,7 @@ import java.lang.ref.WeakReference * Use CloudStreamApp instead. */ // Deprecate after next stable -/* -@Deprecated( +/*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp"), level = DeprecationLevel.WARNING @@ -37,6 +37,15 @@ class AcraApplication { setContext(WeakReference(value)) } + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.removeKeys(folder)"), + level = DeprecationLevel.WARNING + )*/ + fun removeKeys(folder: String): Int? { + return context?.removeKeys(folder) + } + /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(path, value)"), From 1a852f1f4cdd4377600a1799abbd7ef4a9ac2acd Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 6 Dec 2025 08:35:14 -0700 Subject: [PATCH 355/618] Use SharedPreferences.edit extension function (#2299) --- .../lagradost/cloudstream3/MainActivity.kt | 5 +- .../ui/settings/SettingsGeneral.kt | 35 +++++---- .../ui/settings/SettingsPlayer.kt | 78 +++++++++++-------- .../ui/settings/SettingsProviders.kt | 43 ++++++---- .../cloudstream3/ui/settings/SettingsUI.kt | 55 +++++++------ .../ui/settings/SettingsUpdates.kt | 45 ++++++----- .../ui/setup/SetupFragmentLanguage.kt | 6 +- .../ui/setup/SetupFragmentLayout.kt | 7 +- .../ui/setup/SetupFragmentMedia.kt | 7 +- .../ui/setup/SetupFragmentProviderLanguage.kt | 11 ++- .../ui/subtitles/SubtitlesFragment.kt | 8 +- .../cloudstream3/utils/PowerManagerAPI.kt | 8 +- 12 files changed, 180 insertions(+), 128 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 42d9c1869ba..53ba51f525f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -32,6 +32,7 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.edit import androidx.core.view.children import androidx.core.view.get import androidx.core.view.isGone @@ -680,7 +681,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa .setNegativeButton(R.string.no) { _, _ -> /*NO-OP*/ } .setPositiveButton(R.string.yes) { _, _ -> if (dontShowAgainCheck.isChecked) { - settingsManager.edit().putInt(getString(R.string.confirm_exit_key), 1).commit() + settingsManager.edit(commit = true) { + putInt(getString(R.string.confirm_exit_key), 1) + } } // finish() causes a bug on some TVs where player // may keep playing after closing the app. diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index e89865fc44f..4c64b175b76 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.content.edit import androidx.core.os.ConfigurationCompat import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty @@ -156,10 +157,10 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { private val pathPicker = getChooseFolderLauncher { uri, path -> val context = context ?: CloudStreamApp.context ?: return@getChooseFolderLauncher (path ?: uri.toString()).let { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putString(getString(R.string.download_path_key), uri.toString()) - .putString(getString(R.string.download_path_key_visual), it) - .apply() + PreferenceManager.getDefaultSharedPreferences(context).edit { + putString(getString(R.string.download_path_key), uri.toString()) + putString(getString(R.string.download_path_key_visual), it) + } } } @@ -185,7 +186,9 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { try { val langTagIETF = languageTagsIETF[selectedLangIndex] CommonActivity.setLocale(activity, langTagIETF) - settingsManager.edit().putString(getString(R.string.locale_key), langTagIETF).apply() + settingsManager.edit { + putString(getString(R.string.locale_key), langTagIETF) + } activity?.recreate() } catch (e: Exception) { logError(e) @@ -316,7 +319,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { getString(R.string.dns_pref), true, {}) { - settingsManager.edit().putInt(getString(R.string.dns_pref), prefValues[it]).apply() + settingsManager.edit { putInt(getString(R.string.dns_pref), prefValues[it]) } (context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true @@ -341,7 +344,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { } ?: emptyList() } - settingsManager.edit().putBoolean(getString(R.string.jsdelivr_proxy_key), getKey(getString(R.string.jsdelivr_proxy_key), false) ?: false).apply() + settingsManager.edit { putBoolean(getString(R.string.jsdelivr_proxy_key), getKey(getString(R.string.jsdelivr_proxy_key), false) ?: false) } getPref(R.string.jsdelivr_proxy_key)?.setOnPreferenceChangeListener { _, newValue -> setKey(getString(R.string.jsdelivr_proxy_key), newValue) return@setOnPreferenceChangeListener true @@ -371,10 +374,10 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { // Sets both visual and actual paths. // key = used path // visual = visual path - settingsManager.edit() - .putString(getString(R.string.download_path_key), dirs[it]) - .putString(getString(R.string.download_path_key_visual), dirs[it]) - .apply() + settingsManager.edit { + putString(getString(R.string.download_path_key), dirs[it]) + putString(getString(R.string.download_path_key_visual), dirs[it]) + } } } return@setOnPreferenceClickListener true @@ -397,10 +400,12 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { if (beneneCount%20 == 0) { activity?.navigate(R.id.action_navigation_settings_general_to_easterEggMonkeFragment) } - settingsManager.edit().putInt( - getString(R.string.benene_count), - beneneCount - ).apply() + settingsManager.edit { + putInt( + getString(R.string.benene_count), + beneneCount + ) + } it.summary = getString(R.string.benene_count_text).format(beneneCount) } catch (e: Exception) { logError(e) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt index 4a1fad90762..e301e8cc47d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.settings import android.os.Bundle import android.text.format.Formatter.formatShortFileSize import android.view.View +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.VideoClickActionHolder @@ -63,10 +64,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(currentPrefSize), getString(R.string.video_buffer_length_settings), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.video_buffer_length_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.video_buffer_length_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -81,10 +83,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.limit_title), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.prefer_limit_title_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.prefer_limit_title_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -99,10 +102,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.software_decoding), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.software_decoding_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.software_decoding_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -117,10 +121,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.limit_title_rez), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.prefer_limit_title_rez_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.prefer_limit_title_rez_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -144,9 +149,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(currentQuality), getString(R.string.watch_quality_pref), true, - {}) { - settingsManager.edit().putInt(getString(R.string.quality_pref_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.quality_pref_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -168,9 +175,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(currentQuality), getString(R.string.watch_quality_pref_data), true, - {}) { - settingsManager.edit().putInt(getString(R.string.quality_pref_mobile_data_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.quality_pref_mobile_data_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -192,8 +201,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.player_pref), true, - {}) { - settingsManager.edit().putString(getString(R.string.player_default_key), prefValues[it]).apply() + {} + ) { + settingsManager.edit { + putString(getString(R.string.player_default_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -220,10 +232,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(currentPrefSize), getString(R.string.video_buffer_disk_settings), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.video_buffer_disk_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.video_buffer_disk_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -239,10 +252,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(currentPrefSize), getString(R.string.video_buffer_size_settings), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.video_buffer_size_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.video_buffer_size_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt index 8bc3371ea6c..076f17a0aaf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.settings import android.os.Bundle import android.view.View +import androidx.core.content.edit import androidx.navigation.fragment.findNavController import androidx.navigation.NavOptions import androidx.preference.PreferenceManager @@ -46,13 +47,15 @@ class SettingsProviders : BasePreferenceFragmentCompat() { names, currentList, getString(R.string.display_subbed_dubbed_settings), - {}) { selectedList -> + {} + ) { selectedList -> APIRepository.dubStatusActive = selectedList.map { dublist[it] }.toHashSet() - - settingsManager.edit().putStringSet( - this.getString(R.string.display_sub_key), - selectedList.map { names[it] }.toMutableSet() - ).apply() + settingsManager.edit { + putStringSet( + getString(R.string.display_sub_key), + selectedList.map { names[it] }.toMutableSet() + ) + } } } @@ -91,11 +94,14 @@ class SettingsProviders : BasePreferenceFragmentCompat() { names, currentList, getString(R.string.preferred_media_settings), - {}) { selectedList -> - settingsManager.edit().putStringSet( - this.getString(R.string.prefer_media_type_key), - selectedList.map { it.toString() }.toMutableSet() - ).apply() + {} + ) { selectedList -> + settingsManager.edit { + putStringSet( + getString(R.string.prefer_media_type_key), + selectedList.map { it.toString() }.toMutableSet() + ) + } DataStoreHelper.currentHomePage = null //(context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } @@ -119,12 +125,15 @@ class SettingsProviders : BasePreferenceFragmentCompat() { languagesTagName.map { it.second }, currentIndexList, getString(R.string.provider_lang_settings), - {}) { selectedList -> - settingsManager.edit().putStringSet( - this.getString(R.string.provider_lang_key), - selectedList.map { languagesTagName[it].first }.toSet() - ).apply() - //APIRepository.providersActive = it.context.getApiSettings() + {} + ) { selectedList -> + settingsManager.edit { + putStringSet( + getString(R.string.provider_lang_key), + selectedList.map { languagesTagName[it].first }.toSet() + ) + } + // APIRepository.providersActive = it.context.getApiSettings() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index a991f9297c2..33add0e9505 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.settings import android.os.Build import android.os.Bundle import android.view.View +import androidx.core.content.edit import androidx.preference.PreferenceManager import androidx.preference.SeekBarPreference import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity @@ -81,12 +82,13 @@ class SettingsUI : BasePreferenceFragmentCompat() { prefNames.toList(), prefValues, getString(R.string.poster_ui_settings), - {}) { list -> - val edit = settingsManager.edit() - for ((i, key) in keys.withIndex()) { - edit.putBoolean(key, list.contains(i)) + {} + ) { list -> + settingsManager.edit { + for ((i, key) in keys.withIndex()) { + putBoolean(key, list.contains(i)) + } } - edit.apply() SearchResultBuilder.updateCache(it.context) } @@ -108,9 +110,9 @@ class SettingsUI : BasePreferenceFragmentCompat() { dismissCallback = {}, callback = { try { - settingsManager.edit() - .putInt(getString(R.string.app_layout_key), prefValues[it]) - .apply() + settingsManager.edit { + putInt(getString(R.string.app_layout_key), prefValues[it]) + } context?.updateTv() activity?.recreate() } catch (e: Exception) { @@ -150,11 +152,12 @@ class SettingsUI : BasePreferenceFragmentCompat() { prefValues.indexOf(currentLayout), getString(R.string.app_theme_settings), true, - {}) { + {} + ) { try { - settingsManager.edit() - .putString(getString(R.string.app_theme_key), prefValues[it]) - .apply() + settingsManager.edit { + putString(getString(R.string.app_theme_key), prefValues[it]) + } activity?.recreate() } catch (e: Exception) { logError(e) @@ -187,11 +190,12 @@ class SettingsUI : BasePreferenceFragmentCompat() { prefValues.indexOf(currentLayout), getString(R.string.primary_color_settings), true, - {}) { + {} + ) { try { - settingsManager.edit() - .putString(getString(R.string.primary_color_key), prefValues[it]) - .apply() + settingsManager.edit { + putString(getString(R.string.primary_color_key), prefValues[it]) + } activity?.recreate() } catch (e: Exception) { logError(e) @@ -213,11 +217,14 @@ class SettingsUI : BasePreferenceFragmentCompat() { names, currentList, getString(R.string.pref_filter_search_quality), - {}) { selectedList -> - settingsManager.edit().putStringSet( - this.getString(R.string.pref_filter_search_quality_key), - selectedList.map { it.toString() }.toMutableSet() - ).apply() + {} + ) { selectedList -> + settingsManager.edit { + putStringSet( + getString(R.string.pref_filter_search_quality_key), + selectedList.map { it.toString() }.toMutableSet() + ) + } } return@setOnPreferenceClickListener true @@ -235,9 +242,9 @@ class SettingsUI : BasePreferenceFragmentCompat() { showApply = true, dismissCallback = {}, callback = { selectedOption -> - settingsManager.edit() - .putInt(getString(R.string.confirm_exit_key), prefValues[selectedOption]) - .apply() + settingsManager.edit { + putInt(getString(R.string.confirm_exit_key), prefValues[selectedOption]) + } } ) return@setOnPreferenceClickListener true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 0d34cb9889e..6ff07203888 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.content.edit import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager @@ -59,10 +60,10 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { private val pathPicker = getChooseFolderLauncher { uri, path -> val context = context ?: CloudStreamApp.context ?: return@getChooseFolderLauncher (path ?: uri.toString()).let { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putString(getString(R.string.backup_path_key), uri.toString()) - .putString(getString(R.string.backup_dir_key), it) - .apply() + PreferenceManager.getDefaultSharedPreferences(context).edit { + putString(getString(R.string.backup_path_key), uri.toString()) + putString(getString(R.string.backup_dir_key), it) + } } } @@ -87,9 +88,11 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.backup_frequency), true, - {}) { index -> - settingsManager.edit() - .putInt(getString(R.string.automatic_backup_key), prefValues[index]).apply() + {} + ) { index -> + settingsManager.edit { + putInt(getString(R.string.automatic_backup_key), prefValues[index]) + } BackupWorkManager.enqueuePeriodicWork( context ?: CloudStreamApp.context, prefValues[index].toLong() @@ -118,7 +121,8 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { dirs.indexOf(currentDir), getString(R.string.backup_path_title), true, - {}) { + {} + ) { // Last = custom if (it == dirs.size) { try { @@ -130,10 +134,10 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { // Sets both visual and actual paths. // path = used uri // dir = dir path - settingsManager.edit() - .putString(getString(R.string.backup_path_key), dirs[it]) - .putString(getString(R.string.backup_dir_key), dirs[it]) - .apply() + settingsManager.edit { + putString(getString(R.string.backup_path_key), dirs[it]) + putString(getString(R.string.backup_dir_key), dirs[it]) + } } } return@setOnPreferenceClickListener true @@ -210,11 +214,12 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { prefValues.indexOf(currentInstaller), getString(R.string.apk_installer_settings), true, - {}) { num -> + {} + ) { num -> try { - settingsManager.edit() - .putInt(getString(R.string.apk_installer_key), prefValues[num]) - .apply() + settingsManager.edit { + putInt(getString(R.string.apk_installer_key), prefValues[num]) + } } catch (e: Exception) { logError(e) } @@ -251,9 +256,11 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.automatic_plugin_download_mode_title), true, - {}) { num -> - settingsManager.edit() - .putInt(getString(R.string.auto_download_plugins_key), prefValues[num]).apply() + {} + ) { num -> + settingsManager.edit { + putInt(getString(R.string.auto_download_plugins_key), prefValues[num]) + } (context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt index 5ff85c53b4c..e96a662c370 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt @@ -4,6 +4,7 @@ import android.view.View import android.widget.AbsListView import android.widget.ArrayAdapter import androidx.core.content.ContextCompat +import androidx.core.content.edit import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.BuildConfig @@ -62,8 +63,9 @@ class SetupFragmentLanguage : BaseFragment( listview1.setOnItemClickListener { _, _, selectedLangIndex, _ -> val langTagIETF = languageTagsIETF[selectedLangIndex] CommonActivity.setLocale(activity, langTagIETF) - settingsManager.edit().putString(getString(R.string.locale_key), langTagIETF) - .apply() + settingsManager.edit { + putString(getString(R.string.locale_key), langTagIETF) + } } nextBtt.setOnClickListener { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt index 11cc12066fc..4a8e784a145 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.setup import android.view.View import android.widget.AbsListView import android.widget.ArrayAdapter +import androidx.core.content.edit import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey @@ -44,9 +45,9 @@ class SetupFragmentLayout : BaseFragment( ) listview1.setOnItemClickListener { _, _, position, _ -> - settingsManager.edit() - .putInt(getString(R.string.app_layout_key), prefValues[position]) - .apply() + settingsManager.edit { + putInt(getString(R.string.app_layout_key), prefValues[position]) + } activity?.recreate() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt index ca5e63cceaa..8da121daa98 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.setup import android.view.View import android.widget.AbsListView import android.widget.ArrayAdapter +import androidx.core.content.edit import androidx.core.util.forEach import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager @@ -53,9 +54,9 @@ class SetupFragmentMedia : BaseFragment( val itemVal = TvType.valueOf(item) itemVal.ordinal.toString() }.toSet() - settingsManager.edit() - .putStringSet(getString(R.string.prefer_media_type_key), prefValues) - .apply() + settingsManager.edit { + putStringSet(getString(R.string.prefer_media_type_key), prefValues) + } // Regenerate set homepage DataStoreHelper.currentHomePage = null diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt index 6032af56dd4..3c4a09adea8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.setup import android.view.View import android.widget.AbsListView import android.widget.ArrayAdapter +import androidx.core.content.edit import androidx.core.util.forEach import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager @@ -58,10 +59,12 @@ class SetupFragmentProviderLanguage : BaseFragment if (value) selectedLanguages.add(languagesTagName[key].first) } - settingsManager.edit().putStringSet( - ctx.getString(R.string.provider_lang_key), - selectedLanguages.toSet() - ).apply() + settingsManager.edit { + putStringSet( + ctx.getString(R.string.provider_lang_key), + selectedLanguages.toSet() + ) + } } nextBtt.setOnClickListener { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt index 9b0d3121231..09fd23abdf4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt @@ -18,6 +18,7 @@ import android.widget.Toast import androidx.annotation.FontRes import androidx.annotation.OptIn import androidx.annotation.Px +import androidx.core.content.edit import androidx.core.content.res.ResourcesCompat import androidx.media3.common.text.Cue import androidx.media3.common.util.UnstableApi @@ -636,10 +637,9 @@ class SubtitlesFragment : BaseDialogFragment( subtitlesFilterSubLang.setOnCheckedChangeListener { _, b -> context?.let { ctx -> - PreferenceManager.getDefaultSharedPreferences(ctx) - .edit() - .putBoolean(getString(R.string.filter_sub_lang_key), b) - .apply() + PreferenceManager.getDefaultSharedPreferences(ctx).edit { + putBoolean(getString(R.string.filter_sub_lang_key), b) + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt index 0d7a8abc4a1..63f7e155933 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt @@ -9,6 +9,7 @@ import android.os.PowerManager import android.provider.Settings import android.util.Log import androidx.appcompat.app.AlertDialog +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast @@ -38,7 +39,6 @@ object BatteryOptimizationChecker { fun Context.showBatteryOptimizationDialog() { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - try { AlertDialog.Builder(this) .setTitle(R.string.battery_dialog_title) @@ -46,9 +46,9 @@ object BatteryOptimizationChecker { .setMessage(R.string.battery_dialog_message) .setPositiveButton(R.string.ok) { _, _ -> showRequestIgnoreBatteryOptDialog() } .setNegativeButton(R.string.cancel) { _, _ -> - settingsManager.edit() - .putBoolean(getString(R.string.battery_optimisation_key), false) - .apply() + settingsManager.edit { + putBoolean(getString(R.string.battery_optimisation_key), false) + } } .show() } catch (t: Throwable) { From e25847cb647951ab9e7682d7f717f4fb0bcde681 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 7 Dec 2025 15:24:42 -0700 Subject: [PATCH 356/618] Add API for minimum media duration --- .../cloudstream3/ui/player/CS3IPlayer.kt | 24 ++++++++++++------- .../com/lagradost/cloudstream3/MainAPI.kt | 6 +++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index b7712cd796f..41dc052a71e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -1572,16 +1572,22 @@ class CS3IPlayer : IPlayer { } Log.i(TAG, "Rendered first frame") hasUsedFirstRender = true - val invalid = exoPlayer?.duration?.let { duration -> - // Only errors short playback when not playing downloaded files - duration < 20_000L && currentDownloadedFile == null - // Concatenated sources (non 1 periodCount) bypasses the invalid check as exoPlayer.duration gives only the current period - // If you can get the total time that'd be better, but this is already niche. - && exoPlayer?.currentTimeline?.periodCount == 1 - && exoPlayer?.isCurrentMediaItemLive != true - } ?: false - if (invalid) { + // Only errors short playback when not playing downloaded files + val tooShort = if (currentDownloadedFile == null) { + val provider = getApiFromNameNull(currentLink?.source) + val minimumDurationMs = provider?.minimumDurationMs + exoPlayer?.duration?.let { duration -> + minimumDurationMs != null && + duration < minimumDurationMs && + // Concatenated sources (non 1 periodCount) bypasses the invalid check as exoPlayer.duration gives only the current period + // If you can get the total time that'd be better, but this is already niche. + exoPlayer?.currentTimeline?.periodCount == 1 && + exoPlayer?.isCurrentMediaItemLive != true + } ?: false + } else false + + if (tooShort) { releasePlayer(saveTime = false) event(ErrorEvent(InvalidFileException("Too short playback"))) return diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index cce42da19c0..84b01075933 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -557,6 +557,12 @@ abstract class MainAPI { * */ open val loadTimeoutMs: Long? = null + /** + * The minimum media duration in milliseconds. If the duration is smaller + * than this value, it will result in to short playback errors. + */ + @Prerelease + open val minimumDurationMs: Long? = null /** * A set of which ids the provider can open with getLoadUrl() From a46b0ac6e678f5fc5b82d0b1081fa6d8610c3d32 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:35:11 +0100 Subject: [PATCH 357/618] Download selection fix + sub del fix + Del dialog fix (#2308) --- .../ui/download/DownloadChildFragment.kt | 103 +++++++----------- .../ui/download/DownloadFragment.kt | 95 ++++++---------- .../ui/download/DownloadViewModel.kt | 33 +++--- .../cloudstream3/utils/SubtitleUtils.kt | 27 +++-- .../utils/VideoDownloadManager.kt | 2 +- 5 files changed, 105 insertions(+), 155 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt index 08194fd31cb..d44ea0020b5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt @@ -1,16 +1,16 @@ package com.lagradost.cloudstream3.ui.download import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.text.format.Formatter.formatShortFileSize import android.view.View +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.BaseFragment import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.result.FOCUS_SELF @@ -55,22 +55,6 @@ class DownloadChildFragment : BaseFragment( } override fun onBindingCreated(binding: FragmentChildDownloadsBinding) { - /** - * We never want to retain multi-delete state - * when navigating to downloads. Setting this state - * immediately can sometimes result in the observer - * not being notified in time to update the UI. - * - * By posting to the main looper, we ensure that this - * operation is executed after the view has been fully created - * and all initializations are completed, allowing the - * observer to properly receive and handle the state change. - */ - Handler(Looper.getMainLooper()).post { - downloadViewModel.setIsMultiDeleteState(false) - } - - val folder = arguments?.getString("folder") val name = arguments?.getString("name") if (folder == null) { @@ -101,30 +85,56 @@ class DownloadChildFragment : BaseFragment( } (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(cards.value) } + else -> { (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(null) } } } - observe(downloadViewModel.isMultiDeleteState) { isMultiDeleteState -> + + observe(downloadViewModel.selectedBytes) { + updateDeleteButton(downloadViewModel.selectedItemIds.value?.count() ?: 0, it) + } + + + binding.apply { + btnDelete.setOnClickListener { view -> + downloadViewModel.handleMultiDelete(view.context ?: return@setOnClickListener) + } + + btnCancel.setOnClickListener { + downloadViewModel.cancelSelection() + } + + btnToggleAll.setOnClickListener { + val allSelected = downloadViewModel.isAllChildrenSelected() + if (allSelected) { + downloadViewModel.clearSelectedItems() + } else { + downloadViewModel.selectAllChildren() + } + } + } + + observeNullable(downloadViewModel.selectedItemIds) { selection -> + val isMultiDeleteState = selection != null val adapter = binding.downloadChildList.adapter as? DownloadAdapter adapter?.setIsMultiDeleteState(isMultiDeleteState) binding.downloadDeleteAppbar.isVisible = isMultiDeleteState - if (!isMultiDeleteState) { + binding.downloadChildToolbar.isGone = isMultiDeleteState + + if (selection == null) { activity?.detachBackPressedCallback("Downloads") - downloadViewModel.clearSelectedItems() - binding.downloadChildToolbar.isVisible = true + return@observeNullable } - } - observe(downloadViewModel.selectedBytes) { - updateDeleteButton(downloadViewModel.selectedItemIds.value?.count() ?: 0, it) - } - observe(downloadViewModel.selectedItemIds) { - handleSelectedChange(it) - updateDeleteButton(it.count(), downloadViewModel.selectedBytes.value ?: 0L) + activity?.attachBackPressedCallback("Downloads") { + downloadViewModel.cancelSelection() + } + + updateDeleteButton(selection.count(), downloadViewModel.selectedBytes.value ?: 0L) - binding.btnDelete.isVisible = it.isNotEmpty() - binding.selectItemsText.isVisible = it.isEmpty() + binding.btnDelete.isVisible = selection.isNotEmpty() + binding.selectItemsText.isVisible = selection.isEmpty() val allSelected = downloadViewModel.isAllChildrenSelected() if (allSelected) { @@ -160,37 +170,6 @@ class DownloadChildFragment : BaseFragment( } } - private fun handleSelectedChange(selected: Set) { - if (selected.isNotEmpty()) { - binding?.downloadDeleteAppbar?.isVisible = true - binding?.downloadChildToolbar?.isVisible = false - activity?.attachBackPressedCallback("Downloads") { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnDelete?.setOnClickListener { - context?.let { ctx -> - downloadViewModel.handleMultiDelete(ctx) - } - } - - binding?.btnCancel?.setOnClickListener { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnToggleAll?.setOnClickListener { - val allSelected = downloadViewModel.isAllChildrenSelected() - if (allSelected) { - downloadViewModel.clearSelectedItems() - } else { - downloadViewModel.selectAllChildren() - } - } - - downloadViewModel.setIsMultiDeleteState(true) - } - } - private fun updateDeleteButton(count: Int, selectedBytes: Long) { val formattedSize = formatShortFileSize(context, selectedBytes) binding?.btnDelete?.text = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index e3d77abac9c..3bd424640dd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -7,8 +7,6 @@ import android.content.Context import android.content.Intent import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION import android.os.Build -import android.os.Handler -import android.os.Looper import android.text.format.Formatter.formatShortFileSize import android.view.View import android.widget.LinearLayout @@ -28,6 +26,7 @@ import com.lagradost.cloudstream3.isEpisodeBased import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.BaseFragment import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.player.BasicLink @@ -87,21 +86,6 @@ class DownloadFragment : BaseFragment( binding.downloadAppbar.setAppBarNoScrollFlagsOnTV() binding.downloadDeleteAppbar.setAppBarNoScrollFlagsOnTV() - /** - * We never want to retain multi-delete state - * when navigating to downloads. Setting this state - * immediately can sometimes result in the observer - * not being notified in time to update the UI. - * - * By posting to the main looper, we ensure that this - * operation is executed after the view has been fully created - * and all initializations are completed, allowing the - * observer to properly receive and handle the state change. - */ - Handler(Looper.getMainLooper()).post { - downloadViewModel.setIsMultiDeleteState(false) - } - observe(downloadViewModel.headerCards) { cards -> when (cards) { is Resource.Success -> { @@ -161,26 +145,44 @@ class DownloadFragment : BaseFragment( observe(downloadViewModel.selectedBytes) { updateDeleteButton(downloadViewModel.selectedItemIds.value?.count() ?: 0, it) } - observe(downloadViewModel.isMultiDeleteState) { isMultiDeleteState -> + + binding.apply { + btnDelete.setOnClickListener { view -> + downloadViewModel.handleMultiDelete(view.context ?: return@setOnClickListener) + } + + btnCancel.setOnClickListener { + downloadViewModel.cancelSelection() + } + + btnToggleAll.setOnClickListener { + val allSelected = downloadViewModel.isAllHeadersSelected() + if (allSelected) { + downloadViewModel.clearSelectedItems() + } else { + downloadViewModel.selectAllHeaders() + } + } + } + + observeNullable(downloadViewModel.selectedItemIds) { selection -> + val isMultiDeleteState = selection != null val adapter = binding.downloadList.adapter as? DownloadAdapter adapter?.setIsMultiDeleteState(isMultiDeleteState) binding.downloadDeleteAppbar.isVisible = isMultiDeleteState - if (!isMultiDeleteState) { + binding.downloadAppbar.isGone = isMultiDeleteState + + if (selection == null) { activity?.detachBackPressedCallback("Downloads") - downloadViewModel.clearSelectedItems() - // Prevent race condition and make sure - // we don't display it early - if (downloadViewModel.usedBytes.value?.let { it > 0 } == true) { - binding.downloadAppbar.isVisible = true - } + return@observeNullable } - } - observe(downloadViewModel.selectedItemIds) { - handleSelectedChange(it) - updateDeleteButton(it.count(), downloadViewModel.selectedBytes.value ?: 0L) + activity?.attachBackPressedCallback("Downloads") { + downloadViewModel.cancelSelection() + } + updateDeleteButton(selection.count(), downloadViewModel.selectedBytes.value ?: 0L) - binding.btnDelete.isVisible = it.isNotEmpty() - binding.selectItemsText.isVisible = it.isEmpty() + binding.btnDelete.isVisible = selection.isNotEmpty() + binding.selectItemsText.isVisible = selection.isEmpty() val allSelected = downloadViewModel.isAllHeadersSelected() if (allSelected) { @@ -260,37 +262,6 @@ class DownloadFragment : BaseFragment( } } - private fun handleSelectedChange(selected: Set) { - if (selected.isNotEmpty()) { - binding?.downloadDeleteAppbar?.isVisible = true - binding?.downloadAppbar?.isVisible = false - activity?.attachBackPressedCallback("Downloads") { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnDelete?.setOnClickListener { - context?.let { ctx -> - downloadViewModel.handleMultiDelete(ctx) - } - } - - binding?.btnCancel?.setOnClickListener { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnToggleAll?.setOnClickListener { - val allSelected = downloadViewModel.isAllHeadersSelected() - if (allSelected) { - downloadViewModel.clearSelectedItems() - } else { - downloadViewModel.selectAllHeaders() - } - } - - downloadViewModel.setIsMultiDeleteState(true) - } - } - private fun updateDeleteButton(count: Int, selectedBytes: Long) { val formattedSize = formatShortFileSize(context, selectedBytes) binding?.btnDelete?.text = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt index bf81e606987..ee69390ff2b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt @@ -29,7 +29,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext class DownloadViewModel : ViewModel() { - private val _headerCards = ResourceLiveData>(Resource.Loading()) + private val _headerCards = + ResourceLiveData>(Resource.Loading()) val headerCards: LiveData>> = _headerCards private val _childCards = ResourceLiveData>(Resource.Loading()) @@ -47,22 +48,20 @@ class DownloadViewModel : ViewModel() { private val _selectedBytes = ConsistentLiveData(0) val selectedBytes: LiveData = _selectedBytes - private val _isMultiDeleteState = ConsistentLiveData(false) - val isMultiDeleteState: LiveData = _isMultiDeleteState + private val _selectedItemIds = ConsistentLiveData?>(null) + val selectedItemIds: LiveData?> = _selectedItemIds - private val _selectedItemIds = ConsistentLiveData>(mutableSetOf()) - val selectedItemIds: LiveData> = _selectedItemIds - fun setIsMultiDeleteState(value: Boolean) { - _isMultiDeleteState.postValue(value) + fun cancelSelection() { + updateSelectedItems { null } } fun addSelected(itemId: Int) { - updateSelectedItems { it + itemId } + updateSelectedItems { it?.plus(itemId) ?: setOf(itemId) } } fun removeSelected(itemId: Int) { - updateSelectedItems { it - itemId } + updateSelectedItems { it?.minus(itemId) ?: emptySet() } } fun selectAllHeaders() { @@ -97,8 +96,8 @@ class DownloadViewModel : ViewModel() { return currentSelected.size == headers.size && headers.all { it.data.id in currentSelected } } - private fun updateSelectedItems(action: (Set) -> Set) { - val currentSelected = action(selectedItemIds.value ?: mutableSetOf()) + private fun updateSelectedItems(action: (Set?) -> Set?) { + val currentSelected = action(selectedItemIds.value) _selectedItemIds.postValue(currentSelected) postHeaders() postChildren() @@ -115,7 +114,6 @@ class DownloadViewModel : ViewModel() { fun updateHeaderList(context: Context) = viewModelScope.launchSafe { // Do not push loading as it interrupts the UI //_headerCards.postValue(Resource.Loading()) - clearSelectedItems() val visual = withContext(Dispatchers.IO) { val children = context.getKeys(DOWNLOAD_EPISODE_CACHE) @@ -232,7 +230,6 @@ class DownloadViewModel : ViewModel() { fun updateChildList(context: Context, folder: String) = viewModelScope.launchSafe { _childCards.postValue(Resource.Loading()) // always push loading - clearSelectedItems() val visual = withContext(Dispatchers.IO) { context.getKeys(folder).mapNotNull { key -> @@ -260,6 +257,7 @@ class DownloadViewModel : ViewModel() { } private fun removeItems(idsToRemove: Set) = viewModelScope.launchSafe { + _selectedItemIds.postValue(null) postHeaders(_headerCards.success?.filter { it.data.id !in idsToRemove }) postChildren(_childCards.success?.filter { it.data.id !in idsToRemove }) } @@ -368,16 +366,16 @@ class DownloadViewModel : ViewModel() { .joinToString(separator = "\n") { "• $it" } return when { + data.seriesNames.isNotEmpty() && data.names.isEmpty() -> { + context.getString(R.string.delete_message_series_only).format(formattedSeriesNames) + } + data.ids.count() == 1 -> { context.getString(R.string.delete_message).format( data.names.firstOrNull() ) } - data.seriesNames.isNotEmpty() && data.names.isEmpty() -> { - context.getString(R.string.delete_message_series_only).format(formattedSeriesNames) - } - data.parentName != null && data.names.isNotEmpty() -> { context.getString(R.string.delete_message_series_episodes) .format(data.parentName, formattedNames) @@ -406,7 +404,6 @@ class DownloadViewModel : ViewModel() { when (which) { DialogInterface.BUTTON_POSITIVE -> { viewModelScope.launchSafe { - setIsMultiDeleteState(false) deleteFilesAndUpdateSettings(context, ids, this) { successfulIds -> // We always remove parent because if we are deleting from here // and we have it as non-empty, it was triggered on diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt index 66a6e156c76..97be98aea87 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt @@ -2,8 +2,7 @@ package com.lagradost.cloudstream3.utils import android.content.Context import com.lagradost.api.Log -import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFolder -import com.lagradost.safefile.SafeFile +import com.lagradost.cloudstream3.utils.VideoDownloadManager.basePathToFile object SubtitleUtils { @@ -14,16 +13,20 @@ object SubtitleUtils { ) fun deleteMatchingSubtitles(context: Context, info: VideoDownloadManager.DownloadedFileInfo) { - val relative = info.relativePath - val display = info.displayName - val cleanDisplay = cleanDisplayName(display) - - getFolder(context, relative, info.basePath)?.forEach { (name, uri) -> - if (isMatchingSubtitle(name, display, cleanDisplay)) { - val subtitleFile = SafeFile.fromUri(context, uri) - if (subtitleFile == null || subtitleFile.delete() != true) { - Log.e("SubtitleDeletion", "Failed to delete subtitle file: ${subtitleFile?.name()}") - } + val cleanDisplay = cleanDisplayName(info.displayName) + + val base = basePathToFile(context, info.basePath) + val folder = + base?.gotoDirectory(info.relativePath, createMissingDirectories = false) ?: return + val folderFiles = folder.listFiles() ?: return + + for (file in folderFiles) { + val name = file.name() ?: continue + if (!isMatchingSubtitle(name, info.displayName, cleanDisplay)) { + continue + } + if (file.delete() != true) { + Log.e("SubtitleDeletion", "Failed to delete subtitle file: $name") } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 9748bd29673..cdda1186818 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -1628,7 +1628,7 @@ object VideoDownloadManager { * Turns a string to an UniFile. Used for stored string paths such as settings. * Should only be used to get a download path. * */ - private fun basePathToFile(context: Context, path: String?): SafeFile? { + fun basePathToFile(context: Context, path: String?): SafeFile? { return when { path.isNullOrBlank() -> getDefaultDir(context) path.startsWith("content://") -> SafeFile.fromUri(context, path.toUri()) From 8fabb5c572fd1542145ef7b7af507075d04c09c9 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:17:25 -0700 Subject: [PATCH 358/618] Suppress an UnspecifiedRegisterReceiverFlag lint issue (#2316) Part of my work to fix all error level lint issues, in order to eventually enable `failOnError` and ensure better compatability with older API levels and a more consistent reporting of issues. --- .../cloudstream3/ui/player/AbstractPlayerFragment.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index 413bc5d899f..929550dc2a1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -221,11 +221,16 @@ abstract class AbstractPlayerFragment( ) } } + val filter = IntentFilter() filter.addAction(ACTION_MEDIA_CONTROL) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { activity?.registerReceiver(pipReceiver, filter, Context.RECEIVER_EXPORTED) - } else activity?.registerReceiver(pipReceiver, filter) + } else { + @SuppressLint("UnspecifiedRegisterReceiverFlag") + activity?.registerReceiver(pipReceiver, filter) + } + val isPlaying = player.getIsPlaying() val isPlayingValue = if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused From d5eba57bc0a6e026db25de45ade3a29710a6fbf6 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:20:22 -0700 Subject: [PATCH 359/618] Cleanup UnstableApi usage (#2314) * Remove `@UnstableApi` from GeneratorPlayer and use OptIn instead. * Remove `@OptIn` from WebviewFragment as it was unnecessary. * Move `@OptIn` in SaveCaptionStyle to the actual single line we need to OptIn. * Split `setCues` logic to a new method in ChromcastSubtitlesFragment and only add `@OptIn` to that method as it's only necessary there. * Add some missing `@OptIn` annotations to fix all remaining `UnsafeOptInUsageError` lint errors. --- .../com/lagradost/cloudstream3/ui/WebviewFragment.kt | 5 +---- .../cloudstream3/ui/player/AbstractPlayerFragment.kt | 3 +++ .../ui/player/CustomSubtitleDecoderFactory.kt | 8 ++++---- .../cloudstream3/ui/player/FullScreenPlayer.kt | 4 +--- .../cloudstream3/ui/player/GeneratorPlayer.kt | 7 +------ .../ui/subtitles/ChromecastSubtitlesFragment.kt | 10 +++++++++- .../cloudstream3/ui/subtitles/SubtitlesFragment.kt | 5 +++-- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt index efc9b51c9b0..0d951bf6a7a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt @@ -6,9 +6,7 @@ import android.webkit.JavascriptInterface import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient -import androidx.annotation.OptIn import androidx.fragment.app.FragmentActivity -import androidx.media3.common.util.UnstableApi import androidx.navigation.fragment.findNavController import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.USER_AGENT @@ -28,7 +26,6 @@ class WebviewFragment : BaseFragment( } binding.webView.webViewClient = object : WebViewClient() { - @OptIn(UnstableApi::class) override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest? @@ -43,6 +40,7 @@ class WebviewFragment : BaseFragment( return super.shouldOverrideUrlLoading(view, request) } } + binding.webView.apply { WebViewResolver.webViewUserAgent = settings.userAgentString @@ -53,7 +51,6 @@ class WebviewFragment : BaseFragment( loadUrl(url) } - } companion object { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index 929550dc2a1..de04e386f4d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -20,11 +20,13 @@ import android.widget.ImageView import android.widget.ProgressBar import android.widget.Toast import androidx.annotation.LayoutRes +import androidx.annotation.OptIn import androidx.annotation.StringRes import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.media3.common.PlaybackException +import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.MediaSession import androidx.media3.ui.AspectRatioFrameLayout @@ -74,6 +76,7 @@ const val NEXT_WATCH_EPISODE_PERCENTAGE = 90 // when the player should sync the progress of "watched", TODO MAKE SETTING const val UPDATE_SYNC_PROGRESS_PERCENTAGE = 80 +@OptIn(UnstableApi::class) abstract class AbstractPlayerFragment( var player: IPlayer = CS3IPlayer() ) : Fragment() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt index 40aff83e1c3..ffcd836647c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt @@ -35,8 +35,8 @@ import java.nio.charset.Charset /** * @param fallbackFormat used to create a decoder based on mimetype if the subtitle string is not * enough to identify the subtitle format. - **/ -@UnstableApi + */ +@OptIn(UnstableApi::class) class CustomDecoder(private val fallbackFormat: Format?) : SubtitleParser { companion object { fun updateForcedEncoding(context: Context) { @@ -392,7 +392,7 @@ class CustomSubtitleDecoderFactory : SubtitleDecoderFactory { /** * Decoders created here persists across reset() * Do not save state in the decoder which you want to reset (e.g subtitle offset) - **/ + */ override fun createDecoder(format: Format): SubtitleDecoder { val parser = CustomDecoder(format) // Allow garbage collection if player releases the decoder @@ -404,8 +404,8 @@ class CustomSubtitleDecoderFactory : SubtitleDecoderFactory { } } -@OptIn(UnstableApi::class) /** We need to convert the newer SubtitleParser to an older SubtitleDecoder */ +@OptIn(UnstableApi::class) class DelegatingSubtitleDecoder(name: String, private val parser: SubtitleParser) : SimpleSubtitleDecoder(name) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 9cc829e9511..3821d880a1f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -103,6 +103,7 @@ const val DOUBLE_TAB_PAUSE_PERCENTAGE = 0.15 // in both directions private const val SUBTITLE_DELAY_BUNDLE_KEY = "subtitle_delay" // All the UI Logic for the player +@OptIn(UnstableApi::class) open class FullScreenPlayer : AbstractPlayerFragment() { private var isVerticalOrientation: Boolean = false protected open var lockRotation = true @@ -274,7 +275,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { private fun animateLayoutChangesForSubtitles() = // Post here as bottomPlayerBar is gone the first frame => bottomPlayerBar.height = 0 playerBinding?.bottomPlayerBar?.post { - @OptIn(UnstableApi::class) val sView = subView ?: return@post val sStyle = CustomDecoder.style val binding = playerBinding ?: return@post @@ -378,7 +378,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } } - @OptIn(UnstableApi::class) override fun subtitlesChanged() { val tracks = player.getVideoTracks() val isBuiltinSubtitles = tracks.currentTextTracks.all { track -> @@ -1526,7 +1525,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { private var loudnessEnhancer: LoudnessEnhancer? = null - @OptIn(UnstableApi::class) private fun handleVolumeAdjustment( delta: Float, fromButton: Boolean, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 48353736b47..6e362ed801f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -132,7 +132,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.Job import kotlinx.coroutines.launch -@UnstableApi +@OptIn(UnstableApi::class) class GeneratorPlayer : FullScreenPlayer() { companion object { const val NOTIFICATION_ID = 2326 @@ -266,12 +266,8 @@ class GeneratorPlayer : FullScreenPlayer() { return PendingIntent.getBroadcast(context, instanceId, intent, pendingFlags) } - @OptIn(UnstableApi::class) - @UnstableApi private var cachedPlayerNotificationManager: PlayerNotificationManager? = null - @OptIn(UnstableApi::class) - @UnstableApi private fun getMediaNotification(context: Context): PlayerNotificationManager { val cache = cachedPlayerNotificationManager if (cache != null) return cache @@ -876,7 +872,6 @@ class GeneratorPlayer : FullScreenPlayer() { //dialog.subtitles_search_year?.setText(currentTempMeta.year) } - @OptIn(UnstableApi::class) private fun openSubPicker() { try { subsPathPicker.launch( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt index 4f41b436d14..f9b1cb1fe88 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt @@ -10,7 +10,9 @@ import android.util.TypedValue import android.view.View import android.widget.TextView import android.widget.Toast +import androidx.annotation.OptIn import androidx.media3.common.text.Cue +import androidx.media3.common.util.UnstableApi import com.fasterxml.jackson.annotation.JsonProperty import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_DEPRESSED import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_DROP_SHADOW @@ -49,7 +51,7 @@ data class SaveChromeCaptionStyle( @JsonProperty("fontScale") var fontScale: Float = 1.05f, @JsonProperty("windowColor") var windowColor: Int = Color.TRANSPARENT, ) -@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) + class ChromecastSubtitlesFragment : BaseFragment( BaseFragment.BindingCreator.Inflate(ChromecastSubtitleSettingsBinding::inflate) ) { @@ -330,6 +332,12 @@ class ChromecastSubtitlesFragment : BaseFragment( BaseFragment.BindingCreator.Inflate(SubtitleSettingsBinding::inflate) ) { From ae5e25726df720898811ae3047ba339bcd77c3b9 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:31:36 -0700 Subject: [PATCH 360/618] Use String.toUri consistently (#2304) --- .../java/com/lagradost/cloudstream3/MainActivity.kt | 6 +++--- .../cloudstream3/actions/temp/MpvKtPackage.kt | 3 +-- .../lagradost/cloudstream3/actions/temp/MpvPackage.kt | 3 +-- .../cloudstream3/actions/temp/PlayInBrowserAction.kt | 4 ++-- .../cloudstream3/actions/temp/WebVideoCastPackage.kt | 3 +-- .../lagradost/cloudstream3/utils/AppContextUtils.kt | 10 +++++----- .../com/lagradost/cloudstream3/utils/CastHelper.kt | 4 ++-- .../lagradost/cloudstream3/utils/PowerManagerAPI.kt | 4 ++-- .../com/lagradost/cloudstream3/utils/TvChannelUtils.kt | 10 +++++----- 9 files changed, 22 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 53ba51f525f..c12b10f36cb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -9,7 +9,6 @@ import android.content.SharedPreferences import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Rect -import android.net.Uri import android.os.Bundle import android.util.AttributeSet import android.util.Log @@ -33,6 +32,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.edit +import androidx.core.net.toUri import androidx.core.view.children import androidx.core.view.get import androidx.core.view.isGone @@ -344,7 +344,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa activity?.findViewById(R.id.nav_rail_view)?.selectedItemId = R.id.navigation_search } else if (safeURI(str)?.scheme == APP_STRING_PLAYER) { - val uri = Uri.parse(str) + val uri = str.toUri() val name = uri.getQueryParameter("name") val url = URLDecoder.decode(uri.authority, "UTF-8") @@ -1924,7 +1924,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa fun buildMediaQueueItem(video: String): MediaQueueItem { // val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_PHOTO) //movieMetadata.putString(MediaMetadata.KEY_TITLE, "CloudStream") - val mediaInfo = MediaInfo.Builder(Uri.parse(video).toString()) + val mediaInfo = MediaInfo.Builder(video.toUri().toString()) .setStreamType(MediaInfo.STREAM_TYPE_NONE) .setContentType(MimeTypes.IMAGE_JPEG) // .setMetadata(movieMetadata).build() diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt index 102f0ac8bb3..faae3921240 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity import android.content.Context import android.content.Intent -import android.net.Uri import androidx.core.net.toUri import com.lagradost.cloudstream3.actions.OpenInAppAction import com.lagradost.cloudstream3.actions.updateDurationAndPosition @@ -45,7 +44,7 @@ open class MpvKtPackage( intent.apply { putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) - setDataAndType(Uri.parse(link.url), "video/*") + setDataAndType(link.url.toUri(), "video/*") // m3u8 plays, but changing sources feature is not available // makeTempM3U8Intent(activity, this, result) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt index 68e619c92c8..95d05aa3ac9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity import android.content.Context import android.content.Intent -import android.net.Uri import androidx.core.net.toUri import com.lagradost.api.Log import com.lagradost.cloudstream3.actions.OpenInAppAction @@ -44,7 +43,7 @@ open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv putExtra("title", video.name) if (index != null) { - setDataAndType(Uri.parse(result.links.getOrNull(index)?.url ?: return), "video/*") + setDataAndType((result.links.getOrNull(index)?.url ?: return).toUri(), "video/*") } else { makeTempM3U8Intent(context, this, result) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt index 7c1b68c054e..bfd2926bf1c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt @@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.actions.temp import android.content.Context import android.content.Intent -import android.net.Uri +import androidx.core.net.toUri import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.ui.result.LinkLoadingResult @@ -33,7 +33,7 @@ class PlayInBrowserAction: VideoClickAction() { ) { val link = result.links.getOrNull(index ?: 0) ?: return val i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse(link.url) + i.data = link.url.toUri() launch(i) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt index 9f7eee7b820..963221bb343 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import androidx.core.net.toUri import com.lagradost.cloudstream3.USER_AGENT @@ -38,7 +37,7 @@ class WebVideoCastPackage: OpenInAppAction( val link = result.links[index ?: 0] intent.apply { - setDataAndType(Uri.parse(link.url), "video/*") + setDataAndType(link.url.toUri(), "video/*") val title = video.name ?: video.headerName diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt index 8334833e462..0376b68357f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -18,7 +18,6 @@ import android.media.tv.TvContract.Channels.COLUMN_INTERNAL_PROVIDER_ID import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities -import android.net.Uri import android.os.Build import android.os.Handler import android.os.Looper @@ -33,6 +32,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.annotation.WorkerThread import androidx.appcompat.app.AlertDialog +import androidx.core.net.toUri import androidx.core.text.HtmlCompat import androidx.core.text.toSpanned import androidx.core.widget.ContentLoadingProgressBar @@ -170,10 +170,10 @@ object AppContextUtils { ) .setWatchNextType(TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE) .setTitle(title) - .setPosterArtUri(Uri.parse(card.posterUrl)) - .setIntentUri(Uri.parse(card.id?.let { + .setPosterArtUri(card.posterUrl?.toUri()) + .setIntentUri((card.id?.let { "$APP_STRING_RESUME_WATCHING://$it" - } ?: card.url)) + } ?: card.url).toUri()) .setInternalProviderId(card.url) .setLastEngagementTimeUtcMillis( resumeWatching?.updateTime ?: System.currentTimeMillis() @@ -603,7 +603,7 @@ object AppContextUtils { ) = (this.getActivity() ?: activity)?.runOnUiThread { try { val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(url) + intent.data = url.toUri() intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) // activityResultRegistry is used to fall back to webview if a browser is missing diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt index d83731658b7..b48c8d40a69 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt @@ -1,6 +1,6 @@ package com.lagradost.cloudstream3.utils -import android.net.Uri +import androidx.core.net.toUri import androidx.media3.common.MimeTypes import com.google.android.gms.cast.* import com.google.android.gms.cast.framework.CastSession @@ -41,7 +41,7 @@ object CastHelper { val srcPoster = epData.poster ?: holder.poster if (srcPoster != null) { - movieMetadata.addImage(WebImage(Uri.parse(srcPoster))) + movieMetadata.addImage(WebImage(srcPoster.toUri())) } var subIndex = 0 diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt index 63f7e155933..e3c7d68dffd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt @@ -3,13 +3,13 @@ package com.lagradost.cloudstream3.utils import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Build.VERSION.SDK_INT import android.os.PowerManager import android.provider.Settings import android.util.Log import androidx.appcompat.app.AlertDialog import androidx.core.content.edit +import androidx.core.net.toUri import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast @@ -67,7 +67,7 @@ object BatteryOptimizationChecker { try { val intent = Intent().apply { action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - data = Uri.parse("package:$PACKAGE_NAME") + data = "package:$PACKAGE_NAME".toUri() } startActivity(intent) } catch (t: Throwable) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt index 17568c8d20a..798cb9d07d8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt @@ -4,8 +4,8 @@ import android.content.ComponentName import android.content.ContentUris import android.content.Context import android.content.Intent -import android.net.Uri import android.util.Log +import androidx.core.net.toUri import androidx.tvprovider.media.tv.Channel import androidx.tvprovider.media.tv.PreviewProgram import androidx.tvprovider.media.tv.TvContractCompat @@ -84,12 +84,12 @@ object TvChannelUtils { } .setContentId(item.url) .setType(TvContractCompat.PreviewPrograms.TYPE_MOVIE) - .setIntentUri(Uri.parse(csshareUri)) + .setIntentUri(csshareUri.toUri()) .setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_2_3) // Validate poster URL before setting if (!poster.isNullOrBlank() && poster.startsWith("http")) { - builder.setPosterArtUri(Uri.parse(poster)) + builder.setPosterArtUri(poster.toUri()) } val program = builder.build() @@ -135,14 +135,14 @@ object TvChannelUtils { fun createTvChannel(context: Context) { val componentName = ComponentName(context, MainActivity::class.java) - val iconUri = Uri.parse("android.resource://${context.packageName}/mipmap/ic_launcher") + val iconUri = "android.resource://${context.packageName}/mipmap/ic_launcher".toUri() val inputId = TvContractCompat.buildInputId(componentName) val channel = Channel.Builder() .setType(TvContractCompat.Channels.TYPE_PREVIEW) .setAppLinkIconUri(iconUri) .setDisplayName(context.getString(R.string.app_name)) .setAppLinkIntent(Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse("cloudstreamapp://open") + data = "cloudstreamapp://open".toUri() }) .setInputId(inputId) .build() From e0231520d587ec5ab8e7fb9de2928a997e3dcb39 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:07:19 +0530 Subject: [PATCH 361/618] added mpvex (#2309) --- .../com/lagradost/cloudstream3/actions/VideoClickAction.kt | 2 ++ .../com/lagradost/cloudstream3/actions/temp/MpvPackage.kt | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index d4f35f081f7..4843b7617a2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -16,6 +16,7 @@ import com.lagradost.cloudstream3.actions.temp.BiglyBTPackage import com.lagradost.cloudstream3.actions.temp.CopyClipboardAction import com.lagradost.cloudstream3.actions.temp.JustPlayerPackage import com.lagradost.cloudstream3.actions.temp.LibreTorrentPackage +import com.lagradost.cloudstream3.actions.temp.MpvExPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPreviewPackage import com.lagradost.cloudstream3.actions.temp.MpvPackage @@ -51,6 +52,7 @@ object VideoClickActionHolder { // main support external apps VlcPackage(), MpvPackage(), + MpvExPackage(), NextPlayerPackage(), JustPlayerPackage(), FcastAction(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt index 95d05aa3ac9..cd49eb994e0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -17,6 +17,9 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkType // https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904 // https://mpv-android.github.io/mpv-android/intent.html +//https://github.com/marlboro-advance/mpvEx +class MpvExPackage: MpvPackage("mpvEx","app.marlboroadvance.mpvex","app.marlboroadvance.mpvex.ui.player.PlayerActivity") + class MpvYTDLPackage : MpvPackage("MPV YTDL", "is.xyz.mpv.ytdl") { override val sourceTypes = setOf( ExtractorLinkType.VIDEO, @@ -25,10 +28,10 @@ class MpvYTDLPackage : MpvPackage("MPV YTDL", "is.xyz.mpv.ytdl") { ) } -open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv"): OpenInAppAction( +open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv",intentClass:String = "is.xyz.mpv.MPVActivity"): OpenInAppAction( txt(appName), packageName, - "is.xyz.mpv.MPVActivity" + intentClass ) { override val oneSource = true // mpv has poor playlist support on TV override suspend fun putExtra( From 9a9e71354c0068d4d3be5a7d2fd359ffebdf6edb Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:55:00 -0700 Subject: [PATCH 362/618] Remove check for SearchAutoComplete (#2313) --- .../java/com/lagradost/cloudstream3/CommonActivity.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index e4e7c69f4d8..58d65b51641 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -51,7 +51,7 @@ import com.lagradost.cloudstream3.ui.settings.extensions.PluginAdapter import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Event -import com.lagradost.cloudstream3.utils.UIHelper +import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UiText import java.lang.ref.WeakReference @@ -648,6 +648,7 @@ object CommonActivity { else -> null } + // println("NEXT FOCUS : $nextView") if (nextView != null) { nextView.requestFocus() @@ -655,10 +656,8 @@ object CommonActivity { return true } - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && - (act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete) - ) { - UIHelper.showInputMethod(act.currentFocus?.findFocus()) + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && currentFocus is SearchView) { + showInputMethod(currentFocus.findFocus()) } //println("Keycode: $keyCode") @@ -667,7 +666,6 @@ object CommonActivity { // "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}", // Toast.LENGTH_LONG //) - } // if someone else want to override the focus then don't handle the event as it is already From 7ded6a4fa1c16a7234a96aff81996c9a3994b459 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:56:34 -0700 Subject: [PATCH 363/618] Add tools:targetApi to appease lint (#2315) Part of my work to fix all error level lint issues, in order to eventually enable `failOnError` and ensure better compatability with older API levels and a more consistent reporting of issues. --- .../main/res/layout/settings_title_top.xml | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/layout/settings_title_top.xml b/app/src/main/res/layout/settings_title_top.xml index 1e3671a6fdc..64637140551 100644 --- a/app/src/main/res/layout/settings_title_top.xml +++ b/app/src/main/res/layout/settings_title_top.xml @@ -1,22 +1,24 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/settings_top_root" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/primaryGrayBackground" + android:orientation="vertical"> + android:id="@android:id/list_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/primaryBlackBackground" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:targetApi="n" /> + \ No newline at end of file From 350d19bd6be3c0b1084ab6c28869ef2889ba4411 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:27:54 -0700 Subject: [PATCH 364/618] Some minor miscellaneous cleanup (#2306) * Some minor miscellaneous cleanup * Remove classes --- .../lagradost/cloudstream3/CommonActivity.kt | 6 +-- .../HeaderDecorationBindingAdapter.kt | 11 ----- .../cloudstream3/ui/HeaderViewDecoration.kt | 42 ------------------- 3 files changed, 3 insertions(+), 56 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 58d65b51641..2a994b8b74b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -24,6 +24,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.core.content.ContextCompat import androidx.core.view.children +import androidx.core.view.isNotEmpty import androidx.preference.PreferenceManager import com.google.android.gms.cast.framework.CastSession import com.google.android.material.chip.ChipGroup @@ -421,8 +422,7 @@ object CommonActivity { private fun View.hasContent(): Boolean { return isShown && when (this) { - //is RecyclerView -> this.childCount > 0 - is ViewGroup -> this.childCount > 0 + is ViewGroup -> this.isNotEmpty() else -> true } } @@ -452,7 +452,7 @@ object CommonActivity { // if cant focus but visible then break and let android decide // the exception if is the view is a parent and has children that wants focus val hasChildrenThatWantsFocus = (next as? ViewGroup)?.let { parent -> - parent.descendantFocusability == ViewGroup.FOCUS_AFTER_DESCENDANTS && parent.childCount > 0 + parent.descendantFocusability == ViewGroup.FOCUS_AFTER_DESCENDANTS && parent.isNotEmpty() } ?: false if (!next.isFocusable && shown && !hasChildrenThatWantsFocus) return null diff --git a/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt deleted file mode 100644 index 045a7963ad0..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.lagradost.cloudstream3 - -import android.view.LayoutInflater -import androidx.annotation.LayoutRes -import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.ui.HeaderViewDecoration - -fun setHeaderDecoration(view: RecyclerView, @LayoutRes headerViewRes: Int) { - val headerView = LayoutInflater.from(view.context).inflate(headerViewRes, null) - view.addItemDecoration(HeaderViewDecoration(headerView)) -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt deleted file mode 100644 index 40c03012a07..00000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.lagradost.cloudstream3.ui - -import android.graphics.Canvas -import android.graphics.Rect -import android.view.View -import androidx.recyclerview.widget.RecyclerView - -class HeaderViewDecoration(private val customView: View) : RecyclerView.ItemDecoration() { - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - super.onDraw(c, parent, state) - customView.layout(parent.left, 0, parent.right, customView.measuredHeight) - for (i in 0 until parent.childCount) { - val view = parent.getChildAt(i) - if (parent.getChildAdapterPosition(view) == 0) { - c.save() - val height = customView.measuredHeight - val top = view.top - height - c.translate(0f, top.toFloat()) - customView.draw(c) - c.restore() - break - } - } - } - - override fun getItemOffsets( - outRect: Rect, - view: View, - parent: RecyclerView, - state: RecyclerView.State - ) { - if (parent.getChildAdapterPosition(view) == 0) { - customView.measure( - View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec(parent.measuredHeight, View.MeasureSpec.AT_MOST) - ) - outRect.set(0, customView.measuredHeight, 0, 0) - } else { - outRect.setEmpty() - } - } -} \ No newline at end of file From 74ceaf9a3ffeee73a1831d983050da527749f2c6 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:27:36 -0700 Subject: [PATCH 365/618] Add a lint suppression for RestrictedApi (#2312) --- .../java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt index 798cb9d07d8..feecbe312df 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.utils +import android.annotation.SuppressLint import android.content.ComponentName import android.content.ContentUris import android.content.Context @@ -66,6 +67,7 @@ object TvChannelUtils { } /** Insert programs into a channel */ + @SuppressLint("RestrictedApi") fun addPrograms(context: Context, channelId: Long, items: List) { for (item in items) { try { From a836b268495c1c988455eede70203594be09a70a Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:32:32 -0700 Subject: [PATCH 366/618] Cleanup InAppUpdater (#2298) The only functional change here is that the commit in the updater dialog was normalized to what it is everywhere else, meaning it is 7 not 10 characters now. I also have another patch prepared to convert this entire class to an actual object rather than just a class with only a companion object but since that touches every single line due to indentation changes, I decided to split it in order to make it easier to review. --- .../cloudstream3/utils/InAppUpdater.kt | 237 ++++++++---------- 1 file changed, 104 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 12befafe02e..3ad6eb0f7fc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -4,32 +4,34 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri +import android.text.TextUtils import android.util.Log import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.services.PackageInstallerService +import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.io.IOException import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import okio.BufferedSink import okio.buffer import okio.sink -import java.io.File -import android.text.TextUtils -import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit -import com.lagradost.cloudstream3.services.PackageInstallerService -import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus -import java.io.BufferedReader -import java.io.IOException -import java.io.InputStreamReader - class InAppUpdater { companion object { @@ -38,53 +40,51 @@ class InAppUpdater { private const val LOG_TAG = "InAppUpdater" - // === IN APP UPDATER === - data class GithubAsset( + private data class GithubAsset( @JsonProperty("name") val name: String, - @JsonProperty("size") val size: Int, // Size bytes - @JsonProperty("browser_download_url") val browserDownloadUrl: String, // download link + @JsonProperty("size") val size: Int, // Size in bytes + @JsonProperty("browser_download_url") val browserDownloadUrl: String, @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive ) - data class GithubRelease( + private data class GithubRelease( @JsonProperty("tag_name") val tagName: String, // Version code - @JsonProperty("body") val body: String, // Desc + @JsonProperty("body") val body: String, // Description @JsonProperty("assets") val assets: List, - @JsonProperty("target_commitish") val targetCommitish: String, // branch + @JsonProperty("target_commitish") val targetCommitish: String, // Branch @JsonProperty("prerelease") val prerelease: Boolean, - @JsonProperty("node_id") val nodeId: String //Node Id + @JsonProperty("node_id") val nodeId: String, ) - data class GithubObject( - @JsonProperty("sha") val sha: String, // sha 256 hash - @JsonProperty("type") val type: String, // object type + private data class GithubObject( + @JsonProperty("sha") val sha: String, // SHA-256 hash + @JsonProperty("type") val type: String, @JsonProperty("url") val url: String, ) - data class GithubTag( + private data class GithubTag( @JsonProperty("object") val githubObject: GithubObject, ) - data class Update( + private data class Update( @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, @JsonProperty("updateURL") val updateURL: String?, @JsonProperty("updateVersion") val updateVersion: String?, @JsonProperty("changelog") val changelog: String?, - @JsonProperty("updateNodeId") val updateNodeId: String? + @JsonProperty("updateNodeId") val updateNodeId: String?, ) private suspend fun Activity.getAppUpdate(): Update { return try { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if (settingsManager.getBoolean( + if ( + settingsManager.getBoolean( getString(R.string.prerelease_update_key), resources.getBoolean(R.bool.is_prerelease) ) ) { getPreReleaseUpdate() - } else { - getReleaseUpdate() - } + } else getReleaseUpdate() } catch (e: Exception) { Log.e(LOG_TAG, Log.getStackTraceString(e)) Update(false, null, null, null, null) @@ -94,56 +94,44 @@ class InAppUpdater { private suspend fun Activity.getReleaseUpdate(): Update { val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = - parseJson>( - app.get( - url, - headers = headers - ).text - ) + val response = parseJson>( + app.get(url, headers = headers).text + ) val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") - /* - val releases = response.map { it.assets }.flatten() - .filter { it.content_type == "application/vnd.android.package-archive" } - val found = - releases.sortedWith(compareBy { - versionRegex.find(it.name)?.groupValues?.get(2) - }).toList().lastOrNull()*/ - val foundList = - response.filter { rel -> - !rel.prerelease - }.sortedWith(compareBy { release -> - release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> - versionRegex.find( - it1 - )?.groupValues?.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - } - }).toList() + val foundList = response.filter { rel -> + !rel.prerelease + }.sortedWith(compareBy { release -> + release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> + versionRegex.find( + it1 + )?.groupValues?.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + } + }).toList() + val found = foundList.lastOrNull() val foundAsset = found?.assets?.getOrNull(0) val currentVersion = packageName?.let { - packageManager.getPackageInfo( - it, - 0 - ) + packageManager.getPackageInfo(it, 0) } foundAsset?.name?.let { assetName -> val foundVersion = versionRegex.find(assetName) val shouldUpdate = - if (foundAsset.browserDownloadUrl != "" && foundVersion != null) currentVersion?.versionName?.let { versionName -> - versionRegexLocal.find(versionName)?.groupValues?.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - }?.compareTo( - foundVersion.groupValues.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - )!! < 0 else false + if (foundAsset.browserDownloadUrl != "" && foundVersion != null) { + currentVersion?.versionName?.let { versionName -> + versionRegexLocal.find(versionName)?.groupValues?.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + }?.compareTo( + foundVersion.groupValues.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + )!! < 0 + } else false return if (foundVersion != null) { Update( shouldUpdate, @@ -152,57 +140,43 @@ class InAppUpdater { found.body, found.nodeId ) - } else { - Update(false, null, null, null, null) - } + } else Update(false, null, null, null, null) } + return Update(false, null, null, null, null) } private suspend fun Activity.getPreReleaseUpdate(): Update { - val tagUrl = - "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" + val tagUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = - parseJson>(app.get(releaseUrl, headers = headers).text) + val response = parseJson>( + app.get(releaseUrl, headers = headers).text + ) + + val found = response.lastOrNull { rel -> + rel.prerelease || rel.tagName == "pre-release" + } - val found = - response.lastOrNull { rel -> - rel.prerelease || rel.tagName == "pre-release" - } val foundAsset = found?.assets?.filter { it -> it.contentType == "application/vnd.android.package-archive" }?.getOrNull(0) - val tagResponse = - parseJson(app.get(tagUrl, headers = headers).text) - - Log.d(LOG_TAG, "Fetched GitHub tag: ${tagResponse.githubObject.sha.take(7)}") - - val shouldUpdate = - (getString(R.string.commit_hash) - .trim { c -> c.isWhitespace() } - .take(7) - != - tagResponse.githubObject.sha - .trim { c -> c.isWhitespace() } - .take(7)) - return if (foundAsset != null) { + val tagResponse = parseJson(app.get(tagUrl, headers = headers).text) + val updateCommitHash = tagResponse.githubObject.sha.trim().take(7) + Log.d(LOG_TAG, "Fetched GitHub tag: $updateCommitHash") + Update( - shouldUpdate, + getString(R.string.commit_hash) != updateCommitHash, foundAsset.browserDownloadUrl, - tagResponse.githubObject.sha.take(10), + updateCommitHash, found.body, found.nodeId ) - } else { - Update(false, null, null, null, null) - } + } else Update(false, null, null, null, null) } - private val updateLock = Mutex() private suspend fun Activity.downloadUpdate(url: String): Boolean { @@ -214,9 +188,7 @@ class InAppUpdater { // Delete all old updates this.cacheDir.listFiles()?.filter { it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix - }?.forEach { - deleteFileOnExit(it) - } + }?.forEach { deleteFileOnExit(it) } val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix") val sink: BufferedSink = downloadedFile.sink().buffer() @@ -226,8 +198,10 @@ class InAppUpdater { sink.close() openApk(this, Uri.fromFile(downloadedFile)) } + return true } catch (e: Exception) { + logError(e) return false } } @@ -255,23 +229,20 @@ class InAppUpdater { /** * @param checkAutoUpdate if the update check was launched automatically - **/ + */ suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if (!checkAutoUpdate || settingsManager.getBoolean( getString(R.string.auto_update_key), true ) ) { val update = getAppUpdate() - if ( - update.shouldUpdate && - update.updateURL != null) { - + if (update.shouldUpdate && update.updateURL != null) { // Check if update should be skipped - val updateNodeId = - settingsManager.getString(getString(R.string.skip_update_key), "") + val updateNodeId = settingsManager.getString( + getString(R.string.skip_update_key), "" + ) // Skips the update if its an automatic update and the update is skipped // This allows updating manually @@ -282,10 +253,7 @@ class InAppUpdater { runOnUiThread { try { val currentVersion = packageName?.let { - packageManager.getPackageInfo( - it, - 0 - ) + packageManager.getPackageInfo(it, 0) } val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) @@ -302,8 +270,6 @@ class InAppUpdater { } // Sanitized because it looks cluttered builder.setMessage(sanitizedChangelog) - - val context = this builder.apply { setPositiveButton(R.string.update) { _, _ -> // Forcefully start any delayed installations @@ -312,42 +278,45 @@ class InAppUpdater { showToast(R.string.download_started, Toast.LENGTH_LONG) // Check if the setting hasn't been changed - if (settingsManager.getInt( + if ( + settingsManager.getInt( getString(R.string.apk_installer_key), -1 ) == -1 ) { - if (isMiUi()) // Set to legacy if using miui - settingsManager.edit() - .putInt(getString(R.string.apk_installer_key), 1) - .apply() + // Set to legacy installer if using MIUI + if (isMiUi()) { + settingsManager.edit { + putInt(getString(R.string.apk_installer_key), 1) + } + } } - val currentInstaller = - settingsManager.getInt( - getString(R.string.apk_installer_key), - 0 - ) + val currentInstaller = settingsManager.getInt( + getString(R.string.apk_installer_key), + 0 + ) when (currentInstaller) { // New method 0 -> { val intent = PackageInstallerService.Companion.getIntent( - context, + this@runAutoUpdate, update.updateURL ) - ContextCompat.startForegroundService(context, intent) + ContextCompat.startForegroundService(this@runAutoUpdate, intent) } // Legacy 1 -> { ioSafe { - if (!downloadUpdate(update.updateURL)) + if (!downloadUpdate(update.updateURL)) { runOnUiThread { showToast( R.string.download_failed, Toast.LENGTH_LONG ) } + } } } } @@ -357,10 +326,12 @@ class InAppUpdater { if (checkAutoUpdate) { setNeutralButton(R.string.skip_update) { _, _ -> - settingsManager.edit().putString( - getString(R.string.skip_update_key), - update.updateNodeId ?: "" - ).apply() + settingsManager.edit { + putString( + getString(R.string.skip_update_key), + update.updateNodeId ?: "" + ) + } } } } @@ -386,7 +357,7 @@ class InAppUpdater { BufferedReader(InputStreamReader(p.inputStream), 1024).use { it.readLine() } - } catch (ex: IOException) { + } catch (_: IOException) { null } } From 70121f45482fbd6cd6551930681a32a7c4151296 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:41:51 -0700 Subject: [PATCH 367/618] Fix crash on Android 5 (#2320) I just realized I hadn't done a PR to fix this issue yet but this issue is why I've been working on fixing all error level lint issues so that we can enable `failOnError` which would have prevented this. --- .../lagradost/cloudstream3/ui/player/FullScreenPlayer.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 3821d880a1f..4be6c250c6f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -1875,7 +1875,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } playerBinding?.apply { - if (isLayout(TV or EMULATOR)) { mapOf( playerGoBack to playerGoBackText, @@ -1990,8 +1989,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() { return@setOnTouchListener handleMotionEvent(callView, event) } - playerControlsScroll.setOnScrollChangeListener { _, _, _, _, _ -> - autoHide() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + playerControlsScroll.setOnScrollChangeListener { _, _, _, _, _ -> + autoHide() + } } exoProgress.setOnTouchListener { _, event -> From 5d2e432614a4841702ce66390e4a1f02b86b5796 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:03:37 -0700 Subject: [PATCH 368/618] Make InAppUpdater an object (#2321) Better than a class with only a companion object I think. --- .../lagradost/cloudstream3/MainActivity.kt | 2 +- .../ui/settings/SettingsUpdates.kt | 2 +- .../cloudstream3/utils/InAppUpdater.kt | 542 +++++++++--------- 3 files changed, 272 insertions(+), 274 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index c12b10f36cb..f6b722feaa5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -159,7 +159,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.ImageLoader.loadImage -import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate +import com.lagradost.cloudstream3.utils.InAppUpdater.runAutoUpdate import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SnackbarHelper.showSnackbar import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 6ff07203888..e2805c4029c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -33,7 +33,7 @@ import com.lagradost.cloudstream3.ui.settings.utils.getChooseFolderLauncher import com.lagradost.cloudstream3.utils.BackupUtils import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate +import com.lagradost.cloudstream3.utils.InAppUpdater.runAutoUpdate import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 3ad6eb0f7fc..45891a5d0a9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -33,333 +33,331 @@ import okio.BufferedSink import okio.buffer import okio.sink -class InAppUpdater { - companion object { - private const val GITHUB_USER_NAME = "recloudstream" - private const val GITHUB_REPO = "cloudstream" - - private const val LOG_TAG = "InAppUpdater" - - private data class GithubAsset( - @JsonProperty("name") val name: String, - @JsonProperty("size") val size: Int, // Size in bytes - @JsonProperty("browser_download_url") val browserDownloadUrl: String, - @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive - ) - - private data class GithubRelease( - @JsonProperty("tag_name") val tagName: String, // Version code - @JsonProperty("body") val body: String, // Description - @JsonProperty("assets") val assets: List, - @JsonProperty("target_commitish") val targetCommitish: String, // Branch - @JsonProperty("prerelease") val prerelease: Boolean, - @JsonProperty("node_id") val nodeId: String, - ) - - private data class GithubObject( - @JsonProperty("sha") val sha: String, // SHA-256 hash - @JsonProperty("type") val type: String, - @JsonProperty("url") val url: String, - ) - - private data class GithubTag( - @JsonProperty("object") val githubObject: GithubObject, - ) - - private data class Update( - @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, - @JsonProperty("updateURL") val updateURL: String?, - @JsonProperty("updateVersion") val updateVersion: String?, - @JsonProperty("changelog") val changelog: String?, - @JsonProperty("updateNodeId") val updateNodeId: String?, - ) - - private suspend fun Activity.getAppUpdate(): Update { - return try { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if ( - settingsManager.getBoolean( - getString(R.string.prerelease_update_key), - resources.getBoolean(R.bool.is_prerelease) - ) - ) { - getPreReleaseUpdate() - } else getReleaseUpdate() - } catch (e: Exception) { - Log.e(LOG_TAG, Log.getStackTraceString(e)) - Update(false, null, null, null, null) - } +object InAppUpdater { + private const val GITHUB_USER_NAME = "recloudstream" + private const val GITHUB_REPO = "cloudstream" + + private const val LOG_TAG = "InAppUpdater" + + private data class GithubAsset( + @JsonProperty("name") val name: String, + @JsonProperty("size") val size: Int, // Size in bytes + @JsonProperty("browser_download_url") val browserDownloadUrl: String, + @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive + ) + + private data class GithubRelease( + @JsonProperty("tag_name") val tagName: String, // Version code + @JsonProperty("body") val body: String, // Description + @JsonProperty("assets") val assets: List, + @JsonProperty("target_commitish") val targetCommitish: String, // Branch + @JsonProperty("prerelease") val prerelease: Boolean, + @JsonProperty("node_id") val nodeId: String, + ) + + private data class GithubObject( + @JsonProperty("sha") val sha: String, // SHA-256 hash + @JsonProperty("type") val type: String, + @JsonProperty("url") val url: String, + ) + + private data class GithubTag( + @JsonProperty("object") val githubObject: GithubObject, + ) + + private data class Update( + @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, + @JsonProperty("updateURL") val updateURL: String?, + @JsonProperty("updateVersion") val updateVersion: String?, + @JsonProperty("changelog") val changelog: String?, + @JsonProperty("updateNodeId") val updateNodeId: String?, + ) + + private suspend fun Activity.getAppUpdate(): Update { + return try { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + if ( + settingsManager.getBoolean( + getString(R.string.prerelease_update_key), + resources.getBoolean(R.bool.is_prerelease) + ) + ) { + getPreReleaseUpdate() + } else getReleaseUpdate() + } catch (e: Exception) { + Log.e(LOG_TAG, Log.getStackTraceString(e)) + Update(false, null, null, null, null) } + } - private suspend fun Activity.getReleaseUpdate(): Update { - val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" - val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = parseJson>( - app.get(url, headers = headers).text - ) + private suspend fun Activity.getReleaseUpdate(): Update { + val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" + val headers = mapOf("Accept" to "application/vnd.github.v3+json") + val response = parseJson>( + app.get(url, headers = headers).text + ) - val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") - val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") - val foundList = response.filter { rel -> - !rel.prerelease - }.sortedWith(compareBy { release -> - release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> - versionRegex.find( - it1 - )?.groupValues?.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } + val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") + val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") + val foundList = response.filter { rel -> + !rel.prerelease + }.sortedWith(compareBy { release -> + release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> + versionRegex.find( + it1 + )?.groupValues?.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() } - }).toList() - - val found = foundList.lastOrNull() - val foundAsset = found?.assets?.getOrNull(0) - val currentVersion = packageName?.let { - packageManager.getPackageInfo(it, 0) } + }).toList() - foundAsset?.name?.let { assetName -> - val foundVersion = versionRegex.find(assetName) - val shouldUpdate = - if (foundAsset.browserDownloadUrl != "" && foundVersion != null) { - currentVersion?.versionName?.let { versionName -> - versionRegexLocal.find(versionName)?.groupValues?.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - }?.compareTo( - foundVersion.groupValues.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - )!! < 0 - } else false - return if (foundVersion != null) { - Update( - shouldUpdate, - foundAsset.browserDownloadUrl, - foundVersion.groupValues[2], - found.body, - found.nodeId - ) - } else Update(false, null, null, null, null) - } - - return Update(false, null, null, null, null) + val found = foundList.lastOrNull() + val foundAsset = found?.assets?.getOrNull(0) + val currentVersion = packageName?.let { + packageManager.getPackageInfo(it, 0) } - private suspend fun Activity.getPreReleaseUpdate(): Update { - val tagUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" - val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" - val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = parseJson>( - app.get(releaseUrl, headers = headers).text - ) - - val found = response.lastOrNull { rel -> - rel.prerelease || rel.tagName == "pre-release" - } - - val foundAsset = found?.assets?.filter { it -> - it.contentType == "application/vnd.android.package-archive" - }?.getOrNull(0) - - return if (foundAsset != null) { - val tagResponse = parseJson(app.get(tagUrl, headers = headers).text) - val updateCommitHash = tagResponse.githubObject.sha.trim().take(7) - Log.d(LOG_TAG, "Fetched GitHub tag: $updateCommitHash") - + foundAsset?.name?.let { assetName -> + val foundVersion = versionRegex.find(assetName) + val shouldUpdate = + if (foundAsset.browserDownloadUrl != "" && foundVersion != null) { + currentVersion?.versionName?.let { versionName -> + versionRegexLocal.find(versionName)?.groupValues?.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + }?.compareTo( + foundVersion.groupValues.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + )!! < 0 + } else false + return if (foundVersion != null) { Update( - getString(R.string.commit_hash) != updateCommitHash, + shouldUpdate, foundAsset.browserDownloadUrl, - updateCommitHash, + foundVersion.groupValues[2], found.body, found.nodeId ) } else Update(false, null, null, null, null) } - private val updateLock = Mutex() + return Update(false, null, null, null, null) + } - private suspend fun Activity.downloadUpdate(url: String): Boolean { - try { - Log.d(LOG_TAG, "Downloading update: $url") - val appUpdateName = "CloudStream" - val appUpdateSuffix = "apk" + private suspend fun Activity.getPreReleaseUpdate(): Update { + val tagUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" + val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" + val headers = mapOf("Accept" to "application/vnd.github.v3+json") + val response = parseJson>( + app.get(releaseUrl, headers = headers).text + ) - // Delete all old updates - this.cacheDir.listFiles()?.filter { - it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix - }?.forEach { deleteFileOnExit(it) } + val found = response.lastOrNull { rel -> + rel.prerelease || rel.tagName == "pre-release" + } - val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix") - val sink: BufferedSink = downloadedFile.sink().buffer() + val foundAsset = found?.assets?.filter { it -> + it.contentType == "application/vnd.android.package-archive" + }?.getOrNull(0) + + return if (foundAsset != null) { + val tagResponse = parseJson(app.get(tagUrl, headers = headers).text) + val updateCommitHash = tagResponse.githubObject.sha.trim().take(7) + Log.d(LOG_TAG, "Fetched GitHub tag: $updateCommitHash") + + Update( + getString(R.string.commit_hash) != updateCommitHash, + foundAsset.browserDownloadUrl, + updateCommitHash, + found.body, + found.nodeId + ) + } else Update(false, null, null, null, null) + } - updateLock.withLock { - sink.writeAll(app.get(url).body.source()) - sink.close() - openApk(this, Uri.fromFile(downloadedFile)) - } + private val updateLock = Mutex() - return true - } catch (e: Exception) { - logError(e) - return false + private suspend fun Activity.downloadUpdate(url: String): Boolean { + try { + Log.d(LOG_TAG, "Downloading update: $url") + val appUpdateName = "CloudStream" + val appUpdateSuffix = "apk" + + // Delete all old updates + this.cacheDir.listFiles()?.filter { + it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix + }?.forEach { deleteFileOnExit(it) } + + val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix") + val sink: BufferedSink = downloadedFile.sink().buffer() + + updateLock.withLock { + sink.writeAll(app.get(url).body.source()) + sink.close() + openApk(this, Uri.fromFile(downloadedFile)) } + + return true + } catch (e: Exception) { + logError(e) + return false } + } - private fun openApk(context: Context, uri: Uri) { - try { - uri.path?.let { - val contentUri = FileProvider.getUriForFile( - context, - BuildConfig.APPLICATION_ID + ".provider", - File(it) - ) - val installIntent = Intent(Intent.ACTION_VIEW).apply { - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) - data = contentUri - } - context.startActivity(installIntent) + private fun openApk(context: Context, uri: Uri) { + try { + uri.path?.let { + val contentUri = FileProvider.getUriForFile( + context, + BuildConfig.APPLICATION_ID + ".provider", + File(it) + ) + val installIntent = Intent(Intent.ACTION_VIEW).apply { + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) + data = contentUri } - } catch (e: Exception) { - logError(e) + context.startActivity(installIntent) } + } catch (e: Exception) { + logError(e) } + } - /** - * @param checkAutoUpdate if the update check was launched automatically - */ - suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if (!checkAutoUpdate || settingsManager.getBoolean( - getString(R.string.auto_update_key), - true + /** + * @param checkAutoUpdate if the update check was launched automatically + */ + suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + if (!checkAutoUpdate || settingsManager.getBoolean( + getString(R.string.auto_update_key), + true + ) + ) { + val update = getAppUpdate() + if (update.shouldUpdate && update.updateURL != null) { + // Check if update should be skipped + val updateNodeId = settingsManager.getString( + getString(R.string.skip_update_key), "" ) - ) { - val update = getAppUpdate() - if (update.shouldUpdate && update.updateURL != null) { - // Check if update should be skipped - val updateNodeId = settingsManager.getString( - getString(R.string.skip_update_key), "" - ) - - // Skips the update if its an automatic update and the update is skipped - // This allows updating manually - if (update.updateNodeId.equals(updateNodeId) && checkAutoUpdate) { - return false - } - runOnUiThread { - try { - val currentVersion = packageName?.let { - packageManager.getPackageInfo(it, 0) - } + // Skips the update if its an automatic update and the update is skipped + // This allows updating manually + if (update.updateNodeId.equals(updateNodeId) && checkAutoUpdate) { + return false + } - val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) - builder.setTitle( - getString(R.string.new_update_format).format( - currentVersion?.versionName, - update.updateVersion - ) + runOnUiThread { + try { + val currentVersion = packageName?.let { + packageManager.getPackageInfo(it, 0) + } + + val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) + builder.setTitle( + getString(R.string.new_update_format).format( + currentVersion?.versionName, + update.updateVersion ) + ) - val logRegex = Regex("\\[(.*?)\\]\\((.*?)\\)") - val sanitizedChangelog = update.changelog?.replace(logRegex) { matchResult -> - matchResult.groupValues[1] - } // Sanitized because it looks cluttered - - builder.setMessage(sanitizedChangelog) - builder.apply { - setPositiveButton(R.string.update) { _, _ -> - // Forcefully start any delayed installations - if (ApkInstaller.delayedInstaller?.startInstallation() == true) return@setPositiveButton - - showToast(R.string.download_started, Toast.LENGTH_LONG) - - // Check if the setting hasn't been changed - if ( - settingsManager.getInt( - getString(R.string.apk_installer_key), - -1 - ) == -1 - ) { - // Set to legacy installer if using MIUI - if (isMiUi()) { - settingsManager.edit { - putInt(getString(R.string.apk_installer_key), 1) - } - } - } + val logRegex = Regex("\\[(.*?)\\]\\((.*?)\\)") + val sanitizedChangelog = update.changelog?.replace(logRegex) { matchResult -> + matchResult.groupValues[1] + } // Sanitized because it looks cluttered + + builder.setMessage(sanitizedChangelog) + builder.apply { + setPositiveButton(R.string.update) { _, _ -> + // Forcefully start any delayed installations + if (ApkInstaller.delayedInstaller?.startInstallation() == true) return@setPositiveButton - val currentInstaller = settingsManager.getInt( + showToast(R.string.download_started, Toast.LENGTH_LONG) + + // Check if the setting hasn't been changed + if ( + settingsManager.getInt( getString(R.string.apk_installer_key), - 0 - ) - - when (currentInstaller) { - // New method - 0 -> { - val intent = PackageInstallerService.Companion.getIntent( - this@runAutoUpdate, - update.updateURL - ) - ContextCompat.startForegroundService(this@runAutoUpdate, intent) + -1 + ) == -1 + ) { + // Set to legacy installer if using MIUI + if (isMiUi()) { + settingsManager.edit { + putInt(getString(R.string.apk_installer_key), 1) } - // Legacy - 1 -> { - ioSafe { - if (!downloadUpdate(update.updateURL)) { - runOnUiThread { - showToast( - R.string.download_failed, - Toast.LENGTH_LONG - ) - } + } + } + + val currentInstaller = settingsManager.getInt( + getString(R.string.apk_installer_key), + 0 + ) + + when (currentInstaller) { + // New method + 0 -> { + val intent = PackageInstallerService.Companion.getIntent( + this@runAutoUpdate, + update.updateURL + ) + ContextCompat.startForegroundService(this@runAutoUpdate, intent) + } + // Legacy + 1 -> { + ioSafe { + if (!downloadUpdate(update.updateURL)) { + runOnUiThread { + showToast( + R.string.download_failed, + Toast.LENGTH_LONG + ) } } } } } + } - setNegativeButton(R.string.cancel) { _, _ -> } + setNegativeButton(R.string.cancel) { _, _ -> } - if (checkAutoUpdate) { - setNeutralButton(R.string.skip_update) { _, _ -> - settingsManager.edit { - putString( - getString(R.string.skip_update_key), - update.updateNodeId ?: "" - ) - } + if (checkAutoUpdate) { + setNeutralButton(R.string.skip_update) { _, _ -> + settingsManager.edit { + putString( + getString(R.string.skip_update_key), + update.updateNodeId ?: "" + ) } } } - builder.show().setDefaultFocus() - } catch (e: Exception) { - logError(e) } + builder.show().setDefaultFocus() + } catch (e: Exception) { + logError(e) } - return true } - return false + return true } return false } + return false + } - private fun isMiUi(): Boolean { - return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name")) - } + private fun isMiUi(): Boolean { + return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name")) + } - private fun getSystemProperty(propName: String): String? { - return try { - val p = Runtime.getRuntime().exec("getprop $propName") - BufferedReader(InputStreamReader(p.inputStream), 1024).use { - it.readLine() - } - } catch (_: IOException) { - null + private fun getSystemProperty(propName: String): String? { + return try { + val p = Runtime.getRuntime().exec("getprop $propName") + BufferedReader(InputStreamReader(p.inputStream), 1024).use { + it.readLine() } + } catch (_: IOException) { + null } } } From eaf2ac07921af26d69285f9127ea6f13fd6d3b54 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:04:11 -0700 Subject: [PATCH 369/618] Add some tools:targetApi to styles to appease lint (#2319) Part of my work to fix all error level lint issues, in order to eventually enable `failOnError` and ensure better compatability with older API levels and a more consistent reporting of issues. --- app/src/main/res/values/styles.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 48687265769..f0d937ea50d 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,4 +1,4 @@ - + + + + + + + From 0d77f7b91ae1bb0f878d425934a63f5481e64af7 Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Sun, 21 Dec 2025 01:27:09 +0000 Subject: [PATCH 401/618] chore(locales): fix locale issues --- app/src/main/res/values-b+af/strings.xml | 2 +- app/src/main/res/values-b+am/strings.xml | 2 +- app/src/main/res/values-b+apc/strings.xml | 2 +- app/src/main/res/values-b+ar/strings.xml | 2 +- app/src/main/res/values-b+ars/strings.xml | 2 +- app/src/main/res/values-b+as/strings.xml | 2 +- app/src/main/res/values-b+az/strings.xml | 2 +- app/src/main/res/values-b+bg/strings.xml | 2 +- app/src/main/res/values-b+bn/strings.xml | 2 +- app/src/main/res/values-b+ckb/strings.xml | 2 +- app/src/main/res/values-b+cs/strings.xml | 2 +- app/src/main/res/values-b+de/strings.xml | 2 +- app/src/main/res/values-b+el/strings.xml | 2 +- app/src/main/res/values-b+eo/strings.xml | 2 +- app/src/main/res/values-b+es/strings.xml | 2 +- app/src/main/res/values-b+fa/strings.xml | 2 +- app/src/main/res/values-b+fil/strings.xml | 2 +- app/src/main/res/values-b+fr/strings.xml | 2 +- app/src/main/res/values-b+gl/strings.xml | 2 +- app/src/main/res/values-b+hi/strings.xml | 2 +- app/src/main/res/values-b+hr/strings.xml | 2 +- app/src/main/res/values-b+hu/strings.xml | 2 +- app/src/main/res/values-b+in/strings.xml | 2 +- app/src/main/res/values-b+it/strings.xml | 2 +- app/src/main/res/values-b+iw/strings.xml | 2 +- app/src/main/res/values-b+ja/strings.xml | 2 +- app/src/main/res/values-b+kn/strings.xml | 2 +- app/src/main/res/values-b+ko/strings.xml | 2 +- app/src/main/res/values-b+lt/strings.xml | 2 +- app/src/main/res/values-b+lv/strings.xml | 2 +- app/src/main/res/values-b+mk/strings.xml | 2 +- app/src/main/res/values-b+ml/strings.xml | 2 +- app/src/main/res/values-b+ms/strings.xml | 2 +- app/src/main/res/values-b+mt/strings.xml | 2 +- app/src/main/res/values-b+my/strings.xml | 2 +- app/src/main/res/values-b+ne/strings.xml | 2 +- app/src/main/res/values-b+nl/strings.xml | 2 +- app/src/main/res/values-b+nn/strings.xml | 2 +- app/src/main/res/values-b+no/strings.xml | 2 +- app/src/main/res/values-b+or/strings.xml | 2 +- app/src/main/res/values-b+pl/strings.xml | 2 +- app/src/main/res/values-b+pt+BR/strings.xml | 2 +- app/src/main/res/values-b+pt/strings.xml | 2 +- app/src/main/res/values-b+qt/strings.xml | 2 +- app/src/main/res/values-b+ro/strings.xml | 2 +- app/src/main/res/values-b+ru/strings.xml | 2 +- app/src/main/res/values-b+sk/strings.xml | 2 +- app/src/main/res/values-b+so/strings.xml | 2 +- app/src/main/res/values-b+sv/strings.xml | 2 +- app/src/main/res/values-b+ta/strings.xml | 2 +- app/src/main/res/values-b+ti/strings.xml | 2 +- app/src/main/res/values-b+tl/strings.xml | 2 +- app/src/main/res/values-b+tr/strings.xml | 2 +- app/src/main/res/values-b+uk/strings.xml | 2 +- app/src/main/res/values-b+ur/strings.xml | 2 +- app/src/main/res/values-b+vi/strings.xml | 2 +- app/src/main/res/values-b+zh+TW/strings.xml | 2 +- app/src/main/res/values-b+zh/strings.xml | 2 +- 58 files changed, 58 insertions(+), 58 deletions(-) diff --git a/app/src/main/res/values-b+af/strings.xml b/app/src/main/res/values-b+af/strings.xml index 9bcfb3f6392..71a18f7a5ff 100644 --- a/app/src/main/res/values-b+af/strings.xml +++ b/app/src/main/res/values-b+af/strings.xml @@ -121,4 +121,4 @@ PIN moet 4 karakters bevat Verkeerde PIN. Probeer weer. Verskaffers - \ No newline at end of file + diff --git a/app/src/main/res/values-b+am/strings.xml b/app/src/main/res/values-b+am/strings.xml index 34b5c34336e..26fb84dd38d 100644 --- a/app/src/main/res/values-b+am/strings.xml +++ b/app/src/main/res/values-b+am/strings.xml @@ -109,4 +109,4 @@ ዓይነቶችን በመጠቀም ይፈልጉ ቅርጸ-ቁምፊዎችን በ%s ውስጥ በማስቀመጥ ያጫኑ hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index b911207ad47..7d3263652e1 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -718,4 +718,4 @@ عدّل صورة الملف ادخل لينك ( عنوان ال URL ) تبع صورة الملف تم تعديل الصورة بنجاح - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ar/strings.xml b/app/src/main/res/values-b+ar/strings.xml index c86e91132fc..ff697d99fa8 100644 --- a/app/src/main/res/values-b+ar/strings.xml +++ b/app/src/main/res/values-b+ar/strings.xml @@ -765,4 +765,4 @@ اعلى وسط اعلى يمين شاهد المسلسل كاملاً - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ars/strings.xml b/app/src/main/res/values-b+ars/strings.xml index dabedeed17c..b8240dc2e5c 100644 --- a/app/src/main/res/values-b+ars/strings.xml +++ b/app/src/main/res/values-b+ars/strings.xml @@ -346,4 +346,4 @@ عنوان مشغل الفيديو بحد أقصى لعدد الأحرف hide_player_control_names_key DNS عبر HTTPS - \ No newline at end of file + diff --git a/app/src/main/res/values-b+as/strings.xml b/app/src/main/res/values-b+as/strings.xml index fd6c358428f..68fc2e16315 100644 --- a/app/src/main/res/values-b+as/strings.xml +++ b/app/src/main/res/values-b+as/strings.xml @@ -665,4 +665,4 @@ সংহতিসমূহ/প্ৰদানকাৰী/পছন্দৰ মাধ্যমত টৰেণ্ট সামৰ্থবান কৰক চফ্টৱেৰ ডিকোডিং চফ্টৱেৰ ডিকোডিঙে প্লেয়াৰক আপোনাৰ ফোন দ্বাৰা সমৰ্থিত নোহোৱা ভিডিঅ\' ফাইলসমূহ চলাবলৈ সামৰ্থবান কৰে, কিন্তু উচ্চ ৰিজ\'লিউচনত লেগি বা অস্থিৰ প্লেবেকৰ সৃষ্টি কৰিব পাৰে - \ No newline at end of file + diff --git a/app/src/main/res/values-b+az/strings.xml b/app/src/main/res/values-b+az/strings.xml index 430cd459350..ffbd9d37d88 100644 --- a/app/src/main/res/values-b+az/strings.xml +++ b/app/src/main/res/values-b+az/strings.xml @@ -144,4 +144,4 @@ Dəstəklənməyən xəta Gözlənilməyən oynadıcı xətası Ekran yansıtma - \ No newline at end of file + diff --git a/app/src/main/res/values-b+bg/strings.xml b/app/src/main/res/values-b+bg/strings.xml index 4116724d122..9eb439c881f 100644 --- a/app/src/main/res/values-b+bg/strings.xml +++ b/app/src/main/res/values-b+bg/strings.xml @@ -703,4 +703,4 @@ Винаги изпращай запитване Задръжте, за да удвоите скоростта Дълго задържане за смяна на скоростта - \ No newline at end of file + diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index 4208f70f088..f65d673aee9 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -352,4 +352,4 @@ প্রস্থান %1$d%2$s hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ckb/strings.xml b/app/src/main/res/values-b+ckb/strings.xml index c47af36a3d4..b38a7956c25 100644 --- a/app/src/main/res/values-b+ckb/strings.xml +++ b/app/src/main/res/values-b+ckb/strings.xml @@ -83,4 +83,4 @@ کۆپی داخستن سەیڤ - \ No newline at end of file + diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index 9da634e7377..07a37beb88a 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -757,4 +757,4 @@ Uprostřed nahoře Vpravo nahoře Přehrát celý seriál - \ No newline at end of file + diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index a6cb66b5257..08b76d92709 100644 --- a/app/src/main/res/values-b+de/strings.xml +++ b/app/src/main/res/values-b+de/strings.xml @@ -729,4 +729,4 @@ Oben links Oben mitte Oben rechts - \ No newline at end of file + diff --git a/app/src/main/res/values-b+el/strings.xml b/app/src/main/res/values-b+el/strings.xml index c5d3e54446f..96da7f2060a 100644 --- a/app/src/main/res/values-b+el/strings.xml +++ b/app/src/main/res/values-b+el/strings.xml @@ -684,4 +684,4 @@ Κάντε όλους τους υπότιτλους πλάγιους Ακτίνα φόντου Σύρετε ξανά προς τα πάνω για να υπερβείτε το 100% - \ No newline at end of file + diff --git a/app/src/main/res/values-b+eo/strings.xml b/app/src/main/res/values-b+eo/strings.xml index 4a23a93da36..e3e4280752a 100644 --- a/app/src/main/res/values-b+eo/strings.xml +++ b/app/src/main/res/values-b+eo/strings.xml @@ -128,4 +128,4 @@ Elŝutante Elŝuto Malsukcesite hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index 35343b9ad68..c4fcc75dc86 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -722,4 +722,4 @@ Inferior izquierda Inferior central Inferior derecho - \ No newline at end of file + diff --git a/app/src/main/res/values-b+fa/strings.xml b/app/src/main/res/values-b+fa/strings.xml index ae6ffae395f..146236651a1 100644 --- a/app/src/main/res/values-b+fa/strings.xml +++ b/app/src/main/res/values-b+fa/strings.xml @@ -352,4 +352,4 @@ ­همه افزونه ها را تست کنید خودکار رنگ اصلی - \ No newline at end of file + diff --git a/app/src/main/res/values-b+fil/strings.xml b/app/src/main/res/values-b+fil/strings.xml index 7ea37085263..f8ba8fa47b2 100644 --- a/app/src/main/res/values-b+fil/strings.xml +++ b/app/src/main/res/values-b+fil/strings.xml @@ -53,4 +53,4 @@ Itago ang napiling quality ng video sa mga resulta ng paghahanap Pumili ng mode upang i-filter ang pag-download ng mga plugin Awtomatikong i-install ang lahat ng hindi pa naka-install na plugin mula sa mga idinagdag na repository. - \ No newline at end of file + diff --git a/app/src/main/res/values-b+fr/strings.xml b/app/src/main/res/values-b+fr/strings.xml index 3edc7fa8843..d8ecc4dae4d 100644 --- a/app/src/main/res/values-b+fr/strings.xml +++ b/app/src/main/res/values-b+fr/strings.xml @@ -723,4 +723,4 @@ En haut au centre En haut à droite Jouer la série complète - \ No newline at end of file + diff --git a/app/src/main/res/values-b+gl/strings.xml b/app/src/main/res/values-b+gl/strings.xml index a5ad765d038..aeb76080e53 100644 --- a/app/src/main/res/values-b+gl/strings.xml +++ b/app/src/main/res/values-b+gl/strings.xml @@ -293,4 +293,4 @@ Redimensionar Omitir introducción Non volver a amosar - \ No newline at end of file + diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index c0ee55e7086..e4eec65f3ae 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -340,4 +340,4 @@ टीवी में देखे Play mirror" कितना hd है वो वाला लेबल - \ No newline at end of file + diff --git a/app/src/main/res/values-b+hr/strings.xml b/app/src/main/res/values-b+hr/strings.xml index e5ae7752d24..902a75500e9 100644 --- a/app/src/main/res/values-b+hr/strings.xml +++ b/app/src/main/res/values-b+hr/strings.xml @@ -743,4 +743,4 @@ Ukloni gledano do ove epizode Ponovo učitano Usluga ponovnog učitavanja - \ No newline at end of file + diff --git a/app/src/main/res/values-b+hu/strings.xml b/app/src/main/res/values-b+hu/strings.xml index 13210d6dd08..1e97719c064 100644 --- a/app/src/main/res/values-b+hu/strings.xml +++ b/app/src/main/res/values-b+hu/strings.xml @@ -592,4 +592,4 @@ Elérhető offline megtekintésre Mindet Kiválaszt Mindent Kijelölés Eltávolítása - \ No newline at end of file + diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index 6f57647b8bd..ca3a39906da 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -753,4 +753,4 @@ Atas tengah Atas kanan Putar Seri Penuh - \ No newline at end of file + diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index 331cdf0618d..56defcbcdbc 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -753,4 +753,4 @@ In alto a destra In centro a sinistra Riproduci serie completa - \ No newline at end of file + diff --git a/app/src/main/res/values-b+iw/strings.xml b/app/src/main/res/values-b+iw/strings.xml index 910b1ee0d8e..558d98a4bc3 100644 --- a/app/src/main/res/values-b+iw/strings.xml +++ b/app/src/main/res/values-b+iw/strings.xml @@ -560,4 +560,4 @@ הראה המלצות מוסיף אפשרות מהירות בנגן תדירות גיבוי - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index dddecc38985..614fe14b935 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -705,4 +705,4 @@ 上中央 右上 全シリーズを再生 - \ No newline at end of file + diff --git a/app/src/main/res/values-b+kn/strings.xml b/app/src/main/res/values-b+kn/strings.xml index fcfaa904526..3a77aeef01d 100644 --- a/app/src/main/res/values-b+kn/strings.xml +++ b/app/src/main/res/values-b+kn/strings.xml @@ -130,4 +130,4 @@ ಈಗಿನ ಎಪಿಸೋಡ್ ಮುಗಿದಾಗ ಮುಂದಿನ ಎಪಿಸೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ko/strings.xml b/app/src/main/res/values-b+ko/strings.xml index 77e6ec74851..7ab55091334 100644 --- a/app/src/main/res/values-b+ko/strings.xml +++ b/app/src/main/res/values-b+ko/strings.xml @@ -659,4 +659,4 @@ %1$d시간 %2$d분 %3$d초 %1$d분 %2$d초 %1$d초 - \ No newline at end of file + diff --git a/app/src/main/res/values-b+lt/strings.xml b/app/src/main/res/values-b+lt/strings.xml index 6493e33e2e1..35719201893 100644 --- a/app/src/main/res/values-b+lt/strings.xml +++ b/app/src/main/res/values-b+lt/strings.xml @@ -255,4 +255,4 @@ Pašalinti iš žiūrimų Garso takelis hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index 1aae0639c16..287d7904807 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -590,4 +590,4 @@ Nepareizs URL vai nederīgs attēls Profila attēls veiksmīgi nomainīts Rādīt ieteikumus - \ No newline at end of file + diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index c70f62fc548..bccc2a00d44 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -709,4 +709,4 @@ Горе во центар Горе на десно Пушти ја целата серија - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ml/strings.xml b/app/src/main/res/values-b+ml/strings.xml index fb956d011b3..dcb9e5270db 100644 --- a/app/src/main/res/values-b+ml/strings.xml +++ b/app/src/main/res/values-b+ml/strings.xml @@ -273,4 +273,4 @@ ഔട്ട്ലൈൻ നിറം പശ്ചാത്തല നിറം hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ms/strings.xml b/app/src/main/res/values-b+ms/strings.xml index 6905abed4a0..8bbb2a7e055 100644 --- a/app/src/main/res/values-b+ms/strings.xml +++ b/app/src/main/res/values-b+ms/strings.xml @@ -538,4 +538,4 @@ Siaran Langsung Audio Podcast - \ No newline at end of file + diff --git a/app/src/main/res/values-b+mt/strings.xml b/app/src/main/res/values-b+mt/strings.xml index bd605bb3782..ca62a043bfd 100644 --- a/app/src/main/res/values-b+mt/strings.xml +++ b/app/src/main/res/values-b+mt/strings.xml @@ -123,4 +123,4 @@ Neħħi Falla t-tniżżil hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+my/strings.xml b/app/src/main/res/values-b+my/strings.xml index b982e2717ad..1d35cbaa0d0 100644 --- a/app/src/main/res/values-b+my/strings.xml +++ b/app/src/main/res/values-b+my/strings.xml @@ -538,4 +538,4 @@ လိုက်ဘရီရွေးချယ်ရန် ဖြင့်ဖွင့်မည် hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ne/strings.xml b/app/src/main/res/values-b+ne/strings.xml index 4eea78b9c7a..9345cab2bf6 100644 --- a/app/src/main/res/values-b+ne/strings.xml +++ b/app/src/main/res/values-b+ne/strings.xml @@ -128,4 +128,4 @@ रिपोजिटरी को नाम र यूआरएल कपी गरियो! hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index e1078212cab..6e0982c79a0 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -634,4 +634,4 @@ Fout: wordt niet ondersteund Beveiliging Accounts - \ No newline at end of file + diff --git a/app/src/main/res/values-b+nn/strings.xml b/app/src/main/res/values-b+nn/strings.xml index 6989a85da31..2cf83c18350 100644 --- a/app/src/main/res/values-b+nn/strings.xml +++ b/app/src/main/res/values-b+nn/strings.xml @@ -192,4 +192,4 @@ Fortsett å sjå Prøv tilkopling på nytt… hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+no/strings.xml b/app/src/main/res/values-b+no/strings.xml index eea8a95fa2f..a981609cfc5 100644 --- a/app/src/main/res/values-b+no/strings.xml +++ b/app/src/main/res/values-b+no/strings.xml @@ -526,4 +526,4 @@ Hjelp Profilbakgrunn hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+or/strings.xml b/app/src/main/res/values-b+or/strings.xml index 8d0f604fbbd..807a3bcc69c 100644 --- a/app/src/main/res/values-b+or/strings.xml +++ b/app/src/main/res/values-b+or/strings.xml @@ -159,4 +159,4 @@ ଏପିସୋଡ୍ %d ମୁକ୍ତିଲାଭ କରିବ ସିଜିନ୍ %1$d ଏପିସୋଡ୍ %2$d ମୁକ୍ତିଲାଭ କରିବ %1$dଘଣ୍ଟା %2$dମିନିଟ୍ %3$dସେକେଣ୍ଡ - \ No newline at end of file + diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index 0a234db9699..5d67069be85 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -734,4 +734,4 @@ Górne środkowe Górne prawe Odtwórz całą serię - \ No newline at end of file + diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index cb2e4b730b6..53df149baac 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -745,4 +745,4 @@ Superior direito Assistir à série completa Resolução e nome - \ No newline at end of file + diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index 31e01661d3d..a789e9739f7 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -731,4 +731,4 @@ Centro em cima Direita em cima Reproduzir Série Inteira - \ No newline at end of file + diff --git a/app/src/main/res/values-b+qt/strings.xml b/app/src/main/res/values-b+qt/strings.xml index 258a30df7af..3de0f32df58 100644 --- a/app/src/main/res/values-b+qt/strings.xml +++ b/app/src/main/res/values-b+qt/strings.xml @@ -654,4 +654,4 @@ Boo Oo-ahh oo-chit ar-ar Boooooo - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ro/strings.xml b/app/src/main/res/values-b+ro/strings.xml index b97397919c8..642eea0c394 100644 --- a/app/src/main/res/values-b+ro/strings.xml +++ b/app/src/main/res/values-b+ro/strings.xml @@ -656,4 +656,4 @@ Încărcați prima disponibilă Șterge pluginul Închide - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ru/strings.xml b/app/src/main/res/values-b+ru/strings.xml index 7589647f63b..a37b0f3735e 100644 --- a/app/src/main/res/values-b+ru/strings.xml +++ b/app/src/main/res/values-b+ru/strings.xml @@ -719,4 +719,4 @@ Вверху центр Вверху правый Смотреть полностью - \ No newline at end of file + diff --git a/app/src/main/res/values-b+sk/strings.xml b/app/src/main/res/values-b+sk/strings.xml index 1b1cba51b41..fb65841f2a2 100644 --- a/app/src/main/res/values-b+sk/strings.xml +++ b/app/src/main/res/values-b+sk/strings.xml @@ -458,4 +458,4 @@ Podcast Všetko Chyba kódovania - \ No newline at end of file + diff --git a/app/src/main/res/values-b+so/strings.xml b/app/src/main/res/values-b+so/strings.xml index 8366f1eea47..fc42c63f7c1 100644 --- a/app/src/main/res/values-b+so/strings.xml +++ b/app/src/main/res/values-b+so/strings.xml @@ -473,4 +473,4 @@ Bilow isku qasan Qoraalka dhamaadka hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml index 6b62e66a0e1..dfbfce4b5d1 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -680,4 +680,4 @@ Betyg%s Uppdatera Plugins Gå till Hämtade filer - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ta/strings.xml b/app/src/main/res/values-b+ta/strings.xml index 368717dfb63..94b6f717a9d 100644 --- a/app/src/main/res/values-b+ta/strings.xml +++ b/app/src/main/res/values-b+ta/strings.xml @@ -677,4 +677,4 @@ %1$d மணி %2$d நிமிடம் %3$d விநாடி %1$d நிமிடம் %2$d விநாடி %1$d விநாடி - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ti/strings.xml b/app/src/main/res/values-b+ti/strings.xml index 99e08335161..6c154c8d857 100644 --- a/app/src/main/res/values-b+ti/strings.xml +++ b/app/src/main/res/values-b+ti/strings.xml @@ -4,4 +4,4 @@ ክፋል %d በ ላይ ይወጣል ተዋሳእቲ፡ %s hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+tl/strings.xml b/app/src/main/res/values-b+tl/strings.xml index cf63eceb65b..94bb8ea1d59 100644 --- a/app/src/main/res/values-b+tl/strings.xml +++ b/app/src/main/res/values-b+tl/strings.xml @@ -258,4 +258,4 @@ Mga setting ng mga subtitle ng Chromecast Maglaro ng Trailer hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index 1e49ba60ad2..f6a125b9100 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -777,4 +777,4 @@ Sağ üst Altyazı Hizalama Tüm Seriyi Oynat - \ No newline at end of file + diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index 2cb4e5da5a0..519e311c359 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -705,4 +705,4 @@ Верхній центр Угорі праворуч Відтворити повну серію - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ur/strings.xml b/app/src/main/res/values-b+ur/strings.xml index 563bf085dc3..d2c3d9f1c7a 100644 --- a/app/src/main/res/values-b+ur/strings.xml +++ b/app/src/main/res/values-b+ur/strings.xml @@ -621,4 +621,4 @@ آواز تاریخ %s اپنے اسمارٹ فون یا کمپیوٹر پر یہ %s وزٹ کریں اور مندرجہ بالا کوڈ ڈالیں - \ No newline at end of file + diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index 8804e087641..aa9caffd1ac 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -746,4 +746,4 @@ Giữa trái Giữa phải Phát trọn bộ loạt phim - \ No newline at end of file + diff --git a/app/src/main/res/values-b+zh+TW/strings.xml b/app/src/main/res/values-b+zh+TW/strings.xml index 7979c9d9546..251df543c5f 100644 --- a/app/src/main/res/values-b+zh+TW/strings.xml +++ b/app/src/main/res/values-b+zh+TW/strings.xml @@ -703,4 +703,4 @@ 軟體解碼 不接受的種子 載入第一個可用的 - \ No newline at end of file + diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 2b149356e8a..0b893327f62 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -777,4 +777,4 @@ 顶部居中 右上 播放全剧 - \ No newline at end of file + From 0593cfbc01c9e3ff7b5bc7f4a6d41adb6aa9367f Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:11:54 -0700 Subject: [PATCH 402/618] Add linting to library (#2301) --- build.gradle.kts | 1 + gradle/libs.versions.toml | 1 + library/build.gradle.kts | 1 + library/lint.xml | 6 +++ .../com/lagradost/cloudstream3/MainAPI.kt | 2 +- .../extractors/ContentXExtractor.kt | 6 +-- .../cloudstream3/extractors/DoodExtractor.kt | 29 +++++++-------- .../cloudstream3/extractors/Filegram.kt | 37 +++++++++---------- .../extractors/RapidVidExtractor.kt | 6 +-- .../cloudstream3/extractors/TRsTXExtractor.kt | 9 ++--- .../extractors/VidMoxyExtractor.kt | 6 +-- .../lagradost/cloudstream3/extractors/Vtbe.kt | 28 +++++++------- .../extractors/helper/CryptoJSHelper.kt | 8 ++-- .../cloudstream3/utils/HlsPlaylistParser.kt | 4 +- 14 files changed, 73 insertions(+), 71 deletions(-) create mode 100644 library/lint.xml diff --git a/build.gradle.kts b/build.gradle.kts index defd2851502..cca263dd422 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.lint) apply false alias(libs.plugins.android.multiplatform.library) apply false alias(libs.plugins.buildkonfig) apply false // Universal build config alias(libs.plugins.dokka) apply false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d29aab0495..24f69824c86 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -112,6 +112,7 @@ work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "w [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +android-lint = { id = "com.android.lint", version.ref = "androidGradlePlugin" } android-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "androidGradlePlugin" } buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfigGradlePlugin" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokkaGradlePlugin" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 5e2b5909882..392552739ff 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -7,6 +7,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { id("maven-publish") // Gradle core plugin + alias(libs.plugins.android.lint) alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.android.multiplatform.library) alias(libs.plugins.buildkonfig) diff --git a/library/lint.xml b/library/lint.xml new file mode 100644 index 00000000000..6f4e202227b --- /dev/null +++ b/library/lint.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 43f7f4070ed..ada8d5cd893 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -2502,7 +2502,7 @@ constructor( fun Episode.addDate(date: String?, format: String = "yyyy-MM-dd") { try { - this.date = SimpleDateFormat(format).parse(date ?: return)?.time + this.date = SimpleDateFormat(format, Locale.getDefault()).parse(date ?: return)?.time } catch (e: Exception) { logError(e) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt index 06c5ec321c5..dba2e926787 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt @@ -17,12 +17,12 @@ open class ContentX : ExtractorApi() { val iSource = app.get(url, referer=extRef).text val iExtract = Regex("""window\.openPlayer\('([^']+)'""").find(iSource)!!.groups[1]?.value ?: throw ErrorLoadingException("iExtract is null") - val subUrls = mutableSetOf() + val subUrls = mutableSetOf() Regex("""\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(iSource).forEach { val (subUrl, subLang) = it.destructured - if (subUrl in subUrls) { return@forEach } - subUrls.add(subUrl) + if (subUrl in subUrls) { return@forEach } + subUrls.add(subUrl) subtitleCallback.invoke( newSubtitleFile( diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt index 58b6396a85a..e1fd47fffab 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt @@ -77,7 +77,7 @@ open class DoodLaExtractor : ExtractorApi() { override var name = "DoodStream" override var mainUrl = "https://dood.la" override val requiresReferer = false - + private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" override suspend fun getUrl( @@ -87,18 +87,17 @@ open class DoodLaExtractor : ExtractorApi() { callback: (ExtractorLink) -> Unit ) { val embedUrl = url.replace("/d/", "/e/") - val req = app.get(embedUrl) + val req = app.get(embedUrl) val host = getBaseUrl(req.url) val response0 = req.text - val md5 = host + (Regex("/pass_md5/[^']*").find(response0)?.value ?: return) + val md5 = host + (Regex("/pass_md5/[^']*").find(response0)?.value ?: return) val trueUrl = app.get(md5, referer = req.url).text + createHashTable() + "?token=" + md5.substringAfterLast("/") - - val quality = Regex("\\d{3,4}p") + val quality = Regex("\\d{3,4}p") .find(response0.substringAfter("").substringBefore("")) ?.groupValues ?.getOrNull(0) - - callback.invoke( + + callback.invoke( newExtractorLink( this.name, this.name, @@ -108,19 +107,17 @@ open class DoodLaExtractor : ExtractorApi() { this.quality = getQualityFromName(quality) } ) - } - -private fun createHashTable(): String { - return buildString { - repeat(10) { - append(alphabet.random()) + + private fun createHashTable(): String { + return buildString { + repeat(10) { + append(alphabet.random()) + } } } -} - -private fun getBaseUrl(url: String): String { + private fun getBaseUrl(url: String): String { return URI(url).let { "${it.scheme}://${it.host}" } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt index df7e0337365..7baa62710ba 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt @@ -32,32 +32,29 @@ open class Filegram : ExtractorApi() { "Sec-Fetch-Site" to "same-site", "user-agent" to USER_AGENT, ) - + val doc = app.get(getEmbedUrl(url), referer = referer).document val unpackedJs = unpackJs(doc).toString() - val videoUrl = Regex("""file:\s*"([^"]+\.m3u8[^"]*)"""").find(unpackedJs)?.groupValues?.get(1) + val videoUrl = Regex("""file:\s*"([^"]+\.m3u8[^"]*)"""").find(unpackedJs)?.groupValues?.get(1) if (videoUrl != null) { M3u8Helper.generateM3u8( - this.name, - fixUrl(videoUrl), - "$mainUrl/", - headers = header - ).forEach(callback) + this.name, + fixUrl(videoUrl), + "$mainUrl/", + headers = header + ).forEach(callback) } } - private fun unpackJs(script: Element): String? { - return script.select("script").find { it.data().contains("eval(function(p,a,c,k,e,d)") } - ?.data()?.let { getAndUnpack(it) } - } - - private fun getEmbedUrl(url: String): String { - return if (!url.contains("/embed-")) { - val videoId = url.substringAfter("$mainUrl/") - "$mainUrl/embed-$videoId" - } else { - url - } - } + private fun unpackJs(script: Element): String? { + return script.select("script").find { it.data().contains("eval(function(p,a,c,k,e,d)") } + ?.data()?.let { getAndUnpack(it) } + } + private fun getEmbedUrl(url: String): String { + return if (!url.contains("/embed-")) { + val videoId = url.substringAfter("$mainUrl/") + "$mainUrl/embed-$videoId" + } else url + } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt index bacd658bbad..9654e5f3805 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt @@ -15,12 +15,12 @@ open class RapidVid : ExtractorApi() { val extRef = referer ?: "" val videoReq = app.get(url, referer=extRef).text - val subUrls = mutableSetOf() + val subUrls = mutableSetOf() Regex("""captions\",\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(videoReq).forEach { val (subUrl, subLang) = it.destructured - if (subUrl in subUrls) { return@forEach } - subUrls.add(subUrl) + if (subUrl in subUrls) { return@forEach } + subUrls.add(subUrl) subtitleCallback.invoke( newSubtitleFile( diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt index c8796896c09..1348f74d501 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt @@ -29,7 +29,7 @@ open class TRsTX : ExtractorApi() { ) } - val vidLinks = mutableSetOf() + val vidLinks = mutableSetOf() val vidMap = mutableListOf>() for (item in postJson) { if (item.file == null || item.title == null) continue @@ -37,8 +37,8 @@ open class TRsTX : ExtractorApi() { val fileUrl = "${mainUrl}/playlist/" + item.file.substring(1) + ".txt" val videoData = app.post(fileUrl, referer=extRef).text - if (videoData in vidLinks) { continue } - vidLinks.add(videoData) + if (videoData in vidLinks) { continue } + vidLinks.add(videoData) vidMap.add(mapOf( "title" to item.title, @@ -46,12 +46,11 @@ open class TRsTX : ExtractorApi() { )) } - for (mapEntry in vidMap) { val title = mapEntry["title"] ?: continue val m3uLink = mapEntry["videoData"] ?: continue - callback.invoke( + callback.invoke( newExtractorLink( source = this.name, name = "${this.name} - ${title}", diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt index e4cb696037d..36acf7f7ad5 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt @@ -15,12 +15,12 @@ open class VidMoxy : ExtractorApi() { val extRef = referer ?: "" val videoReq = app.get(url, referer=extRef).text - val subUrls = mutableSetOf() + val subUrls = mutableSetOf() Regex("""captions\",\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(videoReq).forEach { val (subUrl, subLang) = it.destructured - if (subUrl in subUrls) { return@forEach } - subUrls.add(subUrl) + if (subUrl in subUrls) { return@forEach } + subUrls.add(subUrl) subtitleCallback.invoke( newSubtitleFile( diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt index 0f078b3bb9f..37b8ecb239e 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt @@ -20,20 +20,20 @@ open class Vtbe : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List? { val response = app.get(url,referer=mainUrl).document val extractedpack =response.selectFirst("script:containsData(function(p,a,c,k,e,d))")?.data().toString() - JsUnpacker(extractedpack).unpack()?.let { unPacked -> - Regex("sources:\\[\\{file:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link -> - return listOf( - newExtractorLink( - this.name, - this.name, - link, - ) { - this.referer = referer ?: "" - this.quality = Qualities.Unknown.value - } - ) - } + JsUnpacker(extractedpack).unpack()?.let { unPacked -> + Regex("sources:\\[\\{file:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link -> + return listOf( + newExtractorLink( + this.name, + this.name, + link, + ) { + this.referer = referer ?: "" + this.quality = Qualities.Unknown.value + } + ) } - return null + } + return null } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt index a13db65cc36..af59b6f7d2d 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt @@ -1,7 +1,8 @@ package com.lagradost.cloudstream3.extractors.helper +import com.lagradost.cloudstream3.base64DecodeArray +import com.lagradost.cloudstream3.base64Encode import java.util.Arrays -import java.util.Base64 import java.security.MessageDigest import java.security.SecureRandom import javax.crypto.Cipher @@ -51,8 +52,7 @@ object CryptoJS { System.arraycopy(saltBytes, 0, b, sBytes.size, saltBytes.size) System.arraycopy(cipherText, 0, b, sBytes.size + saltBytes.size, cipherText.size) - val bEncode = Base64.getEncoder().encode(b) - return String(bEncode) + return base64Encode(b) } /** @@ -62,7 +62,7 @@ object CryptoJS { * @param cipherText encrypted string */ fun decrypt(password: String, cipherText: String): String { - val ctBytes = Base64.getDecoder().decode(cipherText.toByteArray()) + val ctBytes = base64DecodeArray(cipherText) val saltBytes = Arrays.copyOfRange(ctBytes, 8, 16) val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt index dbc3c92f6f2..01e5bb86295 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -222,7 +222,7 @@ object HlsPlaylistParser { if (codecs.isNullOrEmpty()) { return arrayOf() } - return split(codecs.trim { it <= ' ' }, "(\\s*,\\s*)") + return split(codecs.trim(), "(\\s*,\\s*)") } fun getCodecsOfType( @@ -928,7 +928,7 @@ object HlsPlaylistParser { fun getMediaMimeType(codecOrNull: String?): String? { var codec = codecOrNull ?: return null - codec = codec.trim { it <= ' ' }.lowercase() + codec = codec.trim().lowercase() if (codec.startsWith("avc1") || codec.startsWith("avc3")) { return MimeTypes.VIDEO_H264 } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { From bc68b3d7c69bdf8e203a6303e1734398b2d90524 Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Sun, 21 Dec 2025 02:19:29 +0000 Subject: [PATCH 403/618] chore(locales): fix locale issues --- app/src/main/res/values-b+hi/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index 7fdb37c28f4..8b7c7965003 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -314,7 +314,7 @@ खोज परिणामों में चयनित वीडियो गुणवत्ता छुपाएं प्रकरण - + %1$d-%2$d @string/home_play From be78306c553dabb83ed19c8d02d668aba2b1d03d Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:22:07 -0700 Subject: [PATCH 404/618] Minor order fix for lint plugin (#2355) --- library/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 392552739ff..e73ed970d96 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -7,8 +7,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { id("maven-publish") // Gradle core plugin - alias(libs.plugins.android.lint) alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.lint) alias(libs.plugins.android.multiplatform.library) alias(libs.plugins.buildkonfig) alias(libs.plugins.dokka) From 6c2228b964a097e0cf558fea638b1a260a3309a7 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:48:37 -0700 Subject: [PATCH 405/618] Improve caching system for actions (#2249) --- .github/workflows/build_to_archive.yml | 6 +++++- .github/workflows/generate_dokka.yml | 6 +++++- .github/workflows/prerelease.yml | 6 +++++- .github/workflows/pull_request.yml | 7 ++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_to_archive.yml b/.github/workflows/build_to_archive.yml index ce920002eb5..07096014afa 100644 --- a/.github/workflows/build_to_archive.yml +++ b/.github/workflows/build_to_archive.yml @@ -40,7 +40,6 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -56,6 +55,11 @@ jobs: echo "::add-mask::${KEY_PWD}" echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Run Gradle run: ./gradlew assemblePrerelease env: diff --git a/.github/workflows/generate_dokka.yml b/.github/workflows/generate_dokka.yml index e082b79f4bd..e3dac385751 100644 --- a/.github/workflows/generate_dokka.yml +++ b/.github/workflows/generate_dokka.yml @@ -45,7 +45,11 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Set up Android SDK uses: android-actions/setup-android@v3 diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index cee9538bd85..c7dee13eb3f 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -31,7 +31,6 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -47,6 +46,11 @@ jobs: echo "::add-mask::${KEY_PWD}" echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Run Gradle run: ./gradlew assemblePrerelease build androidSourcesJar makeJar env: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 381331f0b14..090e7a2ec0f 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -13,11 +13,16 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + cache-read-only: false + - name: Run Gradle run: ./gradlew assemblePrereleaseDebug From 7fd490218039a64251d221fe5ce455de87afc755 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 23 Dec 2025 20:00:28 +0100 Subject: [PATCH 406/618] Translated using Weblate (Belarusian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 33.4% (277 of 828 strings) Translated using Weblate (Arabic (Levantine)) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Belarusian) Currently translated at 27.5% (228 of 828 strings) Translated using Weblate (Latvian) Currently translated at 84.9% (703 of 828 strings) Translated using Weblate (Belarusian) Currently translated at 25.4% (211 of 828 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Belarusian) Currently translated at 23.0% (191 of 828 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Italian) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (828 of 828 strings) Co-authored-by: Hosted Weblate Co-authored-by: Juan Rubin Co-authored-by: Massimo Pissarello Co-authored-by: Pizza Party Co-authored-by: Sasha Glazko Co-authored-by: soldado-do-wolfenstein Co-authored-by: Максим Горпиніч Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) Co-authored-by: 大王叫我来巡山 Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/apc/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/be/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/it/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/lv/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pt/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/uk/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hans/ Translation: Cloudstream/App --- app/src/main/res/values-b+apc/strings.xml | 28 +++++- app/src/main/res/values-b+it/strings.xml | 1 + app/src/main/res/values-b+lv/strings.xml | 2 +- app/src/main/res/values-b+pt+BR/strings.xml | 4 + app/src/main/res/values-b+pt/strings.xml | 4 + app/src/main/res/values-b+uk/strings.xml | 1 + app/src/main/res/values-b+zh/strings.xml | 1 + app/src/main/res/values-be/strings.xml | 104 ++++++++++++++++++++ 8 files changed, 142 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index 7d3263652e1..56e1a7e34bb 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -75,7 +75,7 @@ تلقائيًا نَزِل كل الإضافات من الريپويات يللي نزادِت. محي بلش - فيه تِلِفونات م فيها تعوز الطريقة الجديدة لتجديد الآپات. جربو \"الطريقة القديمة\" إزا م عم تنزل التجديدات. + فيه أجهزة م فيها تعوز الطريقة الجديدة لتجديد الآپات. جربو \"الطريقة القديمة\" إزا م عم تنزل التجديدات. بعد ما تسكر \"كلود ستريم\"، بكفي الڤيديو بشِباك زغير فوق غير آپ هيدا المصدر م بيدعم \"كروم كاست\" تنبيش منظّم @@ -670,7 +670,7 @@ أفّي اللودينگ تلقائيًا سماح ب إستعمال الـTorrent بال سَتِنگز/المصادر/المحتوى المفضل سكر الآپ و رجاع فتحه، و قبال دعم التورنت ت تكفي. - السوفتوار ديكودينگ بخلي مشغل الڤيديو يمشّي أنواع فيلات ڤيديو م بيدعمها جهازك، بس هال شي معقول يخلي الڤيديو يعلق، خاصتًا إزا كانت الجودة عالية + السوفتوار ديكودينگ بخلي مشغل الڤيديو يمشّي أنواع فيلات ڤيديو م بيدعمها جهازك، بس هال شي معقول يخلي الڤيديو يعلق، خاصتًا إزا كانت الجودة عالية. سوفتوار ديكودينگ رايتينگ (أوطا) نهار اللي نزل (أجدد) @@ -718,4 +718,28 @@ عدّل صورة الملف ادخل لينك ( عنوان ال URL ) تبع صورة الملف تم تعديل الصورة بنجاح + بلّش كل المسلسل + نزل النسخة التجريبية من الآپ + أصلًا عندك النسخة التجريبية. + فشل تنزيل النسخة التجريبية. + ستعمل مصدر بديل" + كتيبة الحلقة + م لقينا الرابط + الرابط أو الصورة مش صالحة + عتبر الحلقات محدورة لحد هون + وقف إعتبار الحلقات محدورة لحد هون + عملنا ري-لوود + عمول ري-لوود للمصدر + اسم + الجودة وال اسم + ميلة الترجمة + تحت، عال شمال + تحت، بال نُص + تحت، عال يمين + نُص، شمال + نُص النُص + نُص، عال يمين + فوق، عال شمال + فوق، بال نُص + فوق، عال يمين diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index 4ea21be51f1..fee5b56f346 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -756,4 +756,5 @@ Installa la versione pre-release La versione pre-release è già installata. Impossibile installare la versione pre-release. + Testo dell\'episodio diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index e8fb46baa1a..444a59a5ff1 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -85,7 +85,7 @@ Settingi Žanrs Dalities - Atvērt internetā + Atvērt pārlūkā Ieladēts Lādējas Aizvērt diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index 6a6ba211105..3fbc4fb2830 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -745,4 +745,8 @@ Superior direito Assistir à série completa Resolução e nome + Falha ao instalar a versão antecipada. + Versão antecipada instalada. + Instalar versão antecipada + Episódio Text diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index a789e9739f7..23b49195ffb 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -731,4 +731,8 @@ Centro em cima Direita em cima Reproduzir Série Inteira + Instalar versão de pré-lançamento + Versão de pré-lançamento já instalada. + Falha ao instalar pré-lançamento. + Texto do Episódio diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index f5c9c2c180c..54562f263b1 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -708,4 +708,5 @@ Встановити передрелізну версію Попередня версія вже встановлена. Не вдалося встановити попередню версію. + Текст епізоду diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 243b56a47ba..224041e4929 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -780,4 +780,5 @@ 安装预发行版 已安装预发行版。 安装预发行版失败。 + 剧集文本 diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index a81f30ef7f0..2111713d6dd 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -52,4 +52,108 @@ Прайграць фільм Прайграць трэйлер Прайграць трансляцыю + Трансліраваць Torrent + Прайграць серыял поўнасцю + Гэта відэа — Torrent, гэта значыць, што ваша актыўнасць можа быць адсочана.\nУпэўніцеся, што вы ведаеце, як працуюць Torrent-файлы, перад працягам. + Крыніцы + Субцітры + Паспрабаваць перападлучыцца… + Назад + Прайграць серыю + Спампаваць + Спампавана + Ідзе спампоўванне + Спампоўванне прыпынена + Спампоўванне пачалося + Не ўдалося спампаваць + Спампоўванне скасавана + Спампоўванне завершана + Выберыце элементы для выдалення + Спамповак пакуль што няма. + Даступна для прагляду па-за сеткай + Выбраць ўсё + Зняць выбар + Пачалося абнаўленне + Сеткавая трансляцыя + Адкрыць лакальнае відэа + Памылка пры загрузцы спасылак + Спасылкі перазагружаны + Унутранае сховішча + Дуб + Суб + Выдаліць файл + Прайграць файл + Узнавіць спампоўванне + Прыпыніць спампоўванне + Дадатковыя звесткі + Схаваць + Прайграць + Звесткі + Фільтр закладак + Закладкі + Выдаліць + Задаць статус прагляду + Прымяніць + Скапіраваць + Закрыць + Ачысціць + Захаваць + Назва рэпазіторыя і спасылка + скапіравана! + Паведамленне аб новай серыі + Пошук у іншых пашырэннях + Паказваць рэкамендацыі + Хуткасць прайгравальніка + Налады субцітраў + Колер тэксту + Колер контуру + Колер фону + Колер акна + Тып контуру + Уздым субцітраў + Шрыфт + Памер шрыфту + Пошук праз пастаўшчыкоў + Пошук праз тыпы + %d бенена(ў) дадзена распрацоўшчыкам + Бененаў не дадзена + Выбраць мову аўтаматычна + Спампаваць мовы + Мова субцітраў + Утрымайце, каб скінуць + Усталёўвайце шрыфты, перацягваючы іх да %s + Працягнуць прагляд + Выдаліць + Больш інфармацыі + \@string/home_play + Для карэктнай працы гэтага пастаўшчыка можа спатрэбіцца VPN + Гэты пастаўшчык — Torrent, рэкамендуецца VPN + Сайт не пастаўляе метаданых, загрузіць відэа не ўдасца, калі на сайце яго няма. + Апісанне + Сюжэту не знойдзена + Апісання не знойдзена + Паказаць Logcat 🐈 + Журнал + Відарыс у відарысе + Працягвае прайграванне ў мініяцюры зверху іншых праграм + Кнопка змены памеру прайгравальніка + Прыбраць чорныя межы + Субцітры + Налады субцітраў прайгравальніка + Субцітры Chromecast + Налады субцітраў Chromecast + Хуткасць прайгравання + Дадаць параметр хуткасці да прайгравальніка + Чырканне для перамоткі + Правядзіце пальцам з боку ў бок, каб кіраваць пазіцыяй у відэа + Чырканне для змены налад + Правядзіце пальцам уверх або ўніз злева ці справа, каб змяніць яркасць або гучнасць + Аўтаматычнае прайграванне наступнай серыі + Прайграць наступную серыю пасля сканчэння бягучай + Падвойнае націсканне для перамоткі + Падвойнае націсканне для прыпынення + Крок перамоткі (у секундах) + Двойчы націсніце справа ці злева, каб перайсці наперад ці назад + Двойчы націсніце пасярэдзіне, каб прыпыніць прайграванне + Выкарыстоўваць сістэмную яркасць From 063d960c3a0d3c19eef2c38b8b86cf6371c81e68 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:57:28 -0700 Subject: [PATCH 407/618] Pin rhino version (#2369) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 24f69824c86..692fd3b4c79 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ paletteKtx = "1.0.0" preferenceKtx = "1.2.1" previewseekbarMedia3 = "1.1.1.0" qrcodeKotlin = "4.5.0" -rhino = "1.8.1" +rhino = { strictly = "1.8.1" } # Requires minSdk 26 or later beginning at version 1.9.0 safefile = "0.0.8" shimmer = "0.5.0" tmdbJava = "2.13.0" From 3fe6a7853ae08e8df4ccf319e19d87b2d6ba8b01 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 23 Dec 2025 19:07:08 -0700 Subject: [PATCH 408/618] Replace QuickJS with Zipline (#2256) QuickJS was renamed to Zipline all the way back in 2021. Unlike old QuickJS, newer Zipline versions are 16kb aligned. Current Zipline is also compatible back to minSdk 21. --- app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a7536da0db9..e8a07b571df 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -204,12 +204,12 @@ dependencies { // Extensions & Other Libs implementation(libs.jsoup) // HTML Parser implementation(libs.rhino) // Run JavaScript - implementation(libs.quickjs) implementation(libs.fuzzywuzzy) // Library/Ext Searching with Levenshtein Distance implementation(libs.safefile) // To Prevent the URI File Fu*kery coreLibraryDesugaring(libs.desugar.jdk.libs.nio) // NIO Flavor Needed for NewPipeExtractor implementation(libs.conscrypt.android) // To Fix SSL Fu*kery on Android 9 implementation(libs.jackson.module.kotlin) // JSON Parser + implementation(libs.zipline) // Torrent Support implementation(libs.torrentserver) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 692fd3b4c79..f03f2d86866 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,6 +45,7 @@ torrentserver = "7861970e038b35cd8c6918384e49caf26903e09e" tvprovider = "1.1.0" video = "1.0.0" workRuntimeKtx = "2.10.5" +zipline = "1.24.0" jvmTarget = "1.8" jdkToolchain = "17" @@ -100,7 +101,6 @@ palette-ktx = { module = "androidx.palette:palette-ktx", version.ref = "paletteK preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" } previewseekbar-media3 = { module = "com.github.rubensousa:previewseekbar-media3", version.ref = "previewseekbarMedia3" } qrcode-kotlin = { module = "io.github.g0dkar:qrcode-kotlin", version.ref = "qrcodeKotlin" } -quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" } rhino = { module = "org.mozilla:rhino", version.ref = "rhino" } safefile = { module = "com.github.LagradOst:SafeFile", version.ref = "safefile" } shimmer = { module = "com.facebook.shimmer:shimmer", version.ref = "shimmer" } @@ -109,6 +109,7 @@ torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } video = { module = "com.google.android.mediahome:video", version.ref = "video" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } +zipline = { module = "app.cash.zipline:zipline-android", version.ref = "zipline" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } From 81d9ecde674e23ad2d53b335230369aedaea4a51 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 23 Dec 2025 19:21:59 -0700 Subject: [PATCH 409/618] Move untranslatable strings to seperate file (#2273) This could cause crashes or poisoned data on some languages as some untranslatable strings were being translated, including keys and format strings that shouldn't be translatable. Also when translating the episodes key on weblate it caused a conflict between the plural version (which weblate does support) and the actual episodes key, meaning the episodes key was translating as the singular version of the plural episodes version in some cases. Moving to a separate resource file should hopefully prevent these issues. --- app/src/main/res/values-b+af/strings.xml | 1 - app/src/main/res/values-b+am/strings.xml | 1 - app/src/main/res/values-b+apc/strings.xml | 3 - app/src/main/res/values-b+ar/strings.xml | 22 --- app/src/main/res/values-b+ars/strings.xml | 1 - app/src/main/res/values-b+as/strings.xml | 1 - app/src/main/res/values-b+bg/strings.xml | 3 - app/src/main/res/values-b+bn/strings.xml | 2 - app/src/main/res/values-b+cs/strings.xml | 12 -- app/src/main/res/values-b+de/strings.xml | 1 - app/src/main/res/values-b+el/strings.xml | 1 - app/src/main/res/values-b+eo/strings.xml | 1 - app/src/main/res/values-b+es/array.xml | 44 ----- app/src/main/res/values-b+es/strings.xml | 3 - app/src/main/res/values-b+fa/strings.xml | 1 - app/src/main/res/values-b+fil/strings.xml | 1 - app/src/main/res/values-b+fr/strings.xml | 1 - app/src/main/res/values-b+gl/strings.xml | 5 - app/src/main/res/values-b+hi/strings.xml | 5 - app/src/main/res/values-b+hr/strings.xml | 13 -- app/src/main/res/values-b+hu/strings.xml | 1 - app/src/main/res/values-b+in/strings.xml | 10 -- app/src/main/res/values-b+it/strings.xml | 1 - app/src/main/res/values-b+iw/strings.xml | 1 - app/src/main/res/values-b+ja/strings.xml | 1 - app/src/main/res/values-b+kn/strings.xml | 1 - app/src/main/res/values-b+ko/strings.xml | 1 - app/src/main/res/values-b+lt/strings.xml | 1 - app/src/main/res/values-b+lv/strings.xml | 1 - app/src/main/res/values-b+mk/strings.xml | 1 - app/src/main/res/values-b+ml/strings.xml | 1 - app/src/main/res/values-b+ms/strings.xml | 4 - app/src/main/res/values-b+mt/strings.xml | 1 - app/src/main/res/values-b+my/strings.xml | 1 - app/src/main/res/values-b+ne/strings.xml | 1 - app/src/main/res/values-b+nl/strings.xml | 9 -- app/src/main/res/values-b+nn/strings.xml | 1 - app/src/main/res/values-b+no/strings.xml | 3 - app/src/main/res/values-b+or/strings.xml | 1 - app/src/main/res/values-b+pl/array.xml | 44 ----- app/src/main/res/values-b+pl/strings.xml | 1 - app/src/main/res/values-b+pt+BR/strings.xml | 10 -- app/src/main/res/values-b+pt/strings.xml | 1 - app/src/main/res/values-b+qt/strings.xml | 1 - app/src/main/res/values-b+ro/strings.xml | 2 - app/src/main/res/values-b+ru/strings.xml | 3 - app/src/main/res/values-b+sk/strings.xml | 1 - app/src/main/res/values-b+so/strings.xml | 1 - app/src/main/res/values-b+sv/strings.xml | 3 - app/src/main/res/values-b+ta/strings.xml | 1 - app/src/main/res/values-b+ti/strings.xml | 1 - app/src/main/res/values-b+tl/strings.xml | 1 - app/src/main/res/values-b+tr/array.xml | 44 ----- app/src/main/res/values-b+tr/strings.xml | 35 ---- app/src/main/res/values-b+uk/strings.xml | 3 - app/src/main/res/values-b+ur/strings.xml | 1 - app/src/main/res/values-b+vi/array.xml | 44 ----- app/src/main/res/values-b+vi/strings.xml | 1 - app/src/main/res/values-b+zh+TW/strings.xml | 35 ---- app/src/main/res/values-b+zh/strings.xml | 33 ---- .../res/values/donottranslate-strings.xml | 152 ++++++++++++++++++ app/src/main/res/values/strings.xml | 152 +----------------- 62 files changed, 153 insertions(+), 580 deletions(-) create mode 100644 app/src/main/res/values/donottranslate-strings.xml diff --git a/app/src/main/res/values-b+af/strings.xml b/app/src/main/res/values-b+af/strings.xml index 71a18f7a5ff..81d7a96aec0 100644 --- a/app/src/main/res/values-b+af/strings.xml +++ b/app/src/main/res/values-b+af/strings.xml @@ -105,7 +105,6 @@ Voer lettertipes in deur dit in %s te plaas Rolverdeling: %s Nuwe episode notifikasie - hide_player_control_names_key Gratis Gebruik Wis Uit diff --git a/app/src/main/res/values-b+am/strings.xml b/app/src/main/res/values-b+am/strings.xml index 26fb84dd38d..7fd3274b944 100644 --- a/app/src/main/res/values-b+am/strings.xml +++ b/app/src/main/res/values-b+am/strings.xml @@ -108,5 +108,4 @@ ተጨማሪ መረጃ ዓይነቶችን በመጠቀም ይፈልጉ ቅርጸ-ቁምፊዎችን በ%s ውስጥ በማስቀመጥ ያጫኑ - hide_player_control_names_key diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index 56e1a7e34bb..9bc697acf26 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -573,8 +573,6 @@ حطو الأرقام السرية الحالية صوت حط كبسة لبرم إتجاه الشاشة - rotate_video_key - auto_rotate_video_key برم الشاشة أوتوماتيكيًا برومو غير إتجاه الشاشة أوتوماتيكيًا حسب شكل الڤيديو @@ -625,7 +623,6 @@ تجاهل فتاح الريپو فتاح %s ع تلفونك أو كمپيوترك، وحط الكود اللي فوق - hide_player_control_names_key بلشه من الأول تحذير فتاح الڤيديو اللي ع جهازك diff --git a/app/src/main/res/values-b+ar/strings.xml b/app/src/main/res/values-b+ar/strings.xml index ff697d99fa8..487b29d84f0 100644 --- a/app/src/main/res/values-b+ar/strings.xml +++ b/app/src/main/res/values-b+ar/strings.xml @@ -235,10 +235,6 @@ ملصق مدبلج ملصق مترجم العنوان - show_hd_key - show_dub_key - show_sub_key - show_title_key التحكم في عناصر الواجهة على الملصق لم يتم العثور على تحديثات تحقق من التحديثات @@ -270,8 +266,6 @@ امتداد تكبير إخلاء مسؤولية - legal_notice_key - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. عام زر العشوائي إظهار زر عشوائي على الصفحة الرئيسية والمكتبة @@ -291,10 +285,6 @@ مكان عنوان الملصق وضع العنوان تحت الملصق - anilist_key - mal_key - opensubtitles_key - nginx_key كلمة المرور إسم المستخدم البريد الإلكتروني @@ -302,14 +292,6 @@ إسم الموقع الجديد رابط الموقع مثلا : https://example.com اللغة (الإنجليزية) - %1$s %2$s حساب تسجيل الخروج @@ -332,7 +314,6 @@ الكل الحد الاقصي الحد الأدنى - @string/none الخطوط المحيطة النمط المنخفض ظل @@ -602,8 +583,6 @@ تعديل الحساب تم إعادة تحميل الروابط عرض زر تبديل لاتجاه الشاشة - تدوير الفيديو - مفتاح تدوير الفيديو التلقائي الدوران التلقائي تدوير تمكين التبديل التلقائي لاتجاه الشاشة بناءً على اتجاه الفيديو @@ -652,7 +631,6 @@ قم بزيارة %s على هاتفك الذكي أو جهاز الكمبيوتر وأدخل الرمز أعلاه لا يمكن الحصول على رمز PIN للجهاز، حاول المصادقة المحلية تنتهي صلاحية الرمز خلال %1$dm %2$ds - hide_player_control_names_key تشغيل من البداية فتح فيديو محلي تحذير diff --git a/app/src/main/res/values-b+ars/strings.xml b/app/src/main/res/values-b+ars/strings.xml index b8240dc2e5c..3104e6a9ab4 100644 --- a/app/src/main/res/values-b+ars/strings.xml +++ b/app/src/main/res/values-b+ars/strings.xml @@ -344,6 +344,5 @@ وثائقي موقع عنوان مشغل الفيديو بحد أقصى لعدد الأحرف - hide_player_control_names_key DNS عبر HTTPS diff --git a/app/src/main/res/values-b+as/strings.xml b/app/src/main/res/values-b+as/strings.xml index 68fc2e16315..eb6ad4aa444 100644 --- a/app/src/main/res/values-b+as/strings.xml +++ b/app/src/main/res/values-b+as/strings.xml @@ -607,7 +607,6 @@ চাবটাইটলসমূহ এপিচ\'ড প্লে কৰক প্ৰয়োগ কৰক - hide_player_control_names_key ফাইলসমূহ ডিলিট কৰক ডিলিট (%1$d | %2$s) আপুনি স্থায়ীভাৱে তলত দিয়া আইটেমসমূহ ডিলিট কৰিবলৈ নিশ্চিত নেকি? diff --git a/app/src/main/res/values-b+bg/strings.xml b/app/src/main/res/values-b+bg/strings.xml index 9eb439c881f..2c238b96855 100644 --- a/app/src/main/res/values-b+bg/strings.xml +++ b/app/src/main/res/values-b+bg/strings.xml @@ -296,8 +296,6 @@ НовоИмеНаСайт example.com Езиков код (en) - %1$s %2$s Акаунт Излизане @@ -588,7 +586,6 @@ Покажи предложения Добавя опция за промяна на скоростта в плеъра Този тест е направен за програмисти и не проверява работата на никакви добавки. - hide_player_control_names_key Предстоящо в %s Име на хранилището и URL адрес копирани! diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index f65d673aee9..2e37f43f3d2 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -238,7 +238,6 @@ অ্যান্ড্রয়েড টিভির মতো, কম মেমরির ডিভাইসে খুব বেশি সেট করা হলে সমস্যা করবে। ক্লোন সাইট প্লেয়ারের ফিচার - MAL AniList TMDB IMDB Kitsu Trakt %1$s%2$s অ্যাপ থিম রিকমেন্ডেশনগুলো দেখাও প্লেয়ারে গতির বিকল্প যোগ কর @@ -351,5 +350,4 @@ অ্যাকাউন্ট প্রস্থান %1$d%2$s - hide_player_control_names_key diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index c42cb4c182a..e553ccd7362 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -253,7 +253,6 @@ Roztáhnout Přiblížit Odmítnutí odpovědnosti - Jakékoli právní otázky týkající se obsahu této aplikace je třeba řešit se samotnými hostiteli a poskytovateli souborů, protože s nimi nejsme nijak spojeni. V případě porušení autorských práv se obraťte přímo na odpovědné strany nebo na webové stránky, na kterých se streamování odehrává. Aplikace je určena výhradně pro vzdělávací a osobní účely. CloudStream 3 v aplikaci nehostuje žádný obsah a nemá žádnou kontrolu nad tím, jaká média jsou v aplikaci umístěna nebo odstraněna. CloudStream 3 funguje jako jakýkoli jiný vyhledávač, například Google. Služba CloudStream 3 nehostuje, nenahrává ani nespravuje žádná videa, filmy ani obsah. Pouze vyhledává, agreguje a zobrazuje odkazy v pohodlném, uživatelsky přívětivém rozhraní. Pouze shromažďuje webové stránky třetích stran, které jsou veřejně přístupné prostřednictvím jakéhokoli běžného webového prohlížeče. Je odpovědností uživatele, aby se vyvaroval jakýchkoli akcí, které by mohly porušovat zákony platné v jeho lokalitě. Použijte CloudStream 3 na vlastní nebezpečí. Obecné Náhodné tlačítko Zobrazit na domovské stránce a v knihovně náhodné tlačítko @@ -275,14 +274,6 @@ Uživatelské jméno ahoj@svete.cz 127.0.0.1 - %1$s %2$s účet Odhlásit se @@ -594,8 +585,6 @@ Upravit účet Odkazy znovu načteny Zobrazit tlačítko pro přepnutí otočení obrazovky - rotate_video_key - auto_rotate_video_key Automatické otáčení Otočení Zapnout automatické otáčení obrazovky v závislosti na orientaci videa @@ -644,7 +633,6 @@ Účty Lokální ověření PIN kód vypršel! - hide_player_control_names_key Přehrát od začátku Aktuálně neprobíhají žádná stahování. Otevřít místní video diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index eb5734ca172..67cf55fd77e 100644 --- a/app/src/main/res/values-b+de/strings.xml +++ b/app/src/main/res/values-b+de/strings.xml @@ -602,7 +602,6 @@ Zurücksetzen Akkuverbrauch der App ist bereits auf unbeschränkt eingestellt CloudStreams App-Info kann nicht geöffnet werden. - hide_player_control_names_key Staffel %1$d Episode %2$d wird veröffentlicht in Wird veröffentlicht in %s Sicherheit diff --git a/app/src/main/res/values-b+el/strings.xml b/app/src/main/res/values-b+el/strings.xml index 96da7f2060a..4b671644bda 100644 --- a/app/src/main/res/values-b+el/strings.xml +++ b/app/src/main/res/values-b+el/strings.xml @@ -611,7 +611,6 @@ Τα δεδομένα σας στο CloudStream έχουν κάνει back up. Αν και η πιθανότητα είναι πολύ χαμηλή, όλες οι συσκευές συμπεριφέρονται διαφορετικά. Στη σπάνια περίπτωση, που απαγορευτεί η πρόσβασή σας από την εφαρμογή, διαγράψτε τα δεδομένα εφαρμογής και επαναφέρετέ τα από ένα ήδη υπάρχον backup. Συγνώμη για οποιαδήποτε ταλαιπωρία. Λογαριασμοί Ασφάλεια - hide_player_control_names_key Απόρριψη Ενσωματωμένο Συνδεμένοι diff --git a/app/src/main/res/values-b+eo/strings.xml b/app/src/main/res/values-b+eo/strings.xml index e3e4280752a..f957da0766f 100644 --- a/app/src/main/res/values-b+eo/strings.xml +++ b/app/src/main/res/values-b+eo/strings.xml @@ -127,5 +127,4 @@ Elŝutite Elŝutante Elŝuto Malsukcesite - hide_player_control_names_key diff --git a/app/src/main/res/values-b+es/array.xml b/app/src/main/res/values-b+es/array.xml index 376519bf310..1a7ca460855 100644 --- a/app/src/main/res/values-b+es/array.xml +++ b/app/src/main/res/values-b+es/array.xml @@ -290,48 +290,4 @@ Vietnamita (VISCII) Vietnamita (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index 8cd37933ba2..390c2df586f 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -567,8 +567,6 @@ Editar la cuenta Enlaces recargados Mostrar un botón para cambiar la orientación de la pantalla - rotate_video_key - auto_rotate_video_key Giro automático Girar Activar el cambio automático de la orientación de la pantalla en función de la orientación del vídeo @@ -617,7 +615,6 @@ ¡El código PIN ya ha caducado! El código caduca en %1$d mín y %2$d s No puedo obtener el código PIN del dispositivo; intente con la autenticación local - hide_player_control_names_key Reproducir desde el principio Abrir vídeo de forma local Advertencia diff --git a/app/src/main/res/values-b+fa/strings.xml b/app/src/main/res/values-b+fa/strings.xml index 146236651a1..da6f04d8e60 100644 --- a/app/src/main/res/values-b+fa/strings.xml +++ b/app/src/main/res/values-b+fa/strings.xml @@ -190,7 +190,6 @@ پیش‌فرض کارتون تورنت - hide_player_control_names_key این ارائه‌دهنده تورنتی است، استفاده از VPN توصیه می‌شود بارگزاری پرونده پشتیبانی‌ ارتفاع زیرنویس diff --git a/app/src/main/res/values-b+fil/strings.xml b/app/src/main/res/values-b+fil/strings.xml index f8ba8fa47b2..d4844d1d75c 100644 --- a/app/src/main/res/values-b+fil/strings.xml +++ b/app/src/main/res/values-b+fil/strings.xml @@ -1,6 +1,5 @@ - hide_player_control_names_key Maling PIN. Pakisubukang muli. Alisin ang napanood hanggang sa episode na ito Walang koneksyon sa internet.\n\nKumonekta sa internet at subukang muli, o panoorin ang iyong mga na-download habang ikaw ay offline. diff --git a/app/src/main/res/values-b+fr/strings.xml b/app/src/main/res/values-b+fr/strings.xml index d8ecc4dae4d..a4e669ccf33 100644 --- a/app/src/main/res/values-b+fr/strings.xml +++ b/app/src/main/res/values-b+fr/strings.xml @@ -606,7 +606,6 @@ Verrouillage biométrique Sélectionnez un appareil de diffusion Saison %1$d Episode %2$d sera publié dans - hide_player_control_names_key Regarder depuis le début Ouvrir une vidéo locale Attention diff --git a/app/src/main/res/values-b+gl/strings.xml b/app/src/main/res/values-b+gl/strings.xml index aeb76080e53..1b8f068e364 100644 --- a/app/src/main/res/values-b+gl/strings.xml +++ b/app/src/main/res/values-b+gl/strings.xml @@ -161,7 +161,6 @@ Selecciona o modo para filtrar a descarga dos complementos Instala automáticamente todos os complementos aínda non instalados dos repositorios engadidos. Mostrar actualizacións da aplicación - hide_player_control_names_key Reiniciar aos valores predefinidos -30 Audiolibro @@ -274,10 +273,6 @@ Ligazón copiada ó portapapeis Reproducir capítulo Temporada - - Capítulos - - %1$d-%2$d %1$d %2$s T diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index 8b7c7965003..2c52472382e 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -202,7 +202,6 @@ रूपरेखा रंग उपशीर्षक ऊंचाई मुद्रलिपि - hide_player_control_names_key वर्तमान में कोई डाउनलोड नहीं है। आरम्भ से शुरू करें मिटाने के लिए वस्तु चुनें @@ -312,10 +311,6 @@ लाइब्रेरी मीडिया खोज परिणामों में चयनित वीडियो गुणवत्ता छुपाएं - - प्रकरण - - %1$d-%2$d @string/home_play प्लेयर में आगे पीछे जाने का समय (सेकंड्स) diff --git a/app/src/main/res/values-b+hr/strings.xml b/app/src/main/res/values-b+hr/strings.xml index 902a75500e9..1e2c344abbf 100644 --- a/app/src/main/res/values-b+hr/strings.xml +++ b/app/src/main/res/values-b+hr/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s epizoda %2$d Glumačka postava: %s Epizoda %d će izaći za @@ -285,7 +276,6 @@ Rastegni Zoom Pravna obavijest - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Općenito Gumb za slučajni odabir Prikaži gumb za slučajni odabir na početnoj stranici i biblioteci @@ -597,8 +587,6 @@ Prikaži gumb za prebacivanje orijentacije zaslona Omogućuje automatsko mijenjanje orijentacije zaslona na temelju orijentacije videa Automatsko rotiranje - rotiraj_video_tipka - automatski_rotiraj_video_tipka Obavijest za novu epizodu Traži u drugim proširenjima Dodaje opciju za brzinu u playeru @@ -636,7 +624,6 @@ CloudStream Wiki Računi Sigurnost - hide_player_control_names_key Lokalna autentifikacija Otvori lokalni video Posjeti %s na svom mobitelu ili računalu i unesi gore navedeni kod diff --git a/app/src/main/res/values-b+hu/strings.xml b/app/src/main/res/values-b+hu/strings.xml index 1e97719c064..ae018207b3d 100644 --- a/app/src/main/res/values-b+hu/strings.xml +++ b/app/src/main/res/values-b+hu/strings.xml @@ -579,7 +579,6 @@ A PIN 4 karakter hosszú kell legyen Auto elforgatás Az automatikus videó orientáció alapján való képernyő elforgatás bekapcsolása - hide_player_control_names_key Helyi videó megnyitása Tárhely név és URL Ez a videó egy Torrent, ami azt jelenti, hogy a videótevékenységed nyomon követhető.\nGyőződj meg róla, hogy megérted a torrentezés működését, mielőtt folytatnád. diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index ca3a39906da..17b4f075d0d 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -250,7 +250,6 @@ Regang Zoom Disclaimer - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Umum Tombol Acak Tampilkan tombol acak di Beranda dan Pustaka @@ -267,14 +266,6 @@ Lokasi judul poster Meletakkan judul di bawah poster - %1$s %2$s akun Keluar @@ -632,7 +623,6 @@ CloudStream Wiki Keamanan Akun - hide_player_control_names_key Hati hati Kode PIN kini telah kedaluwarsa! Gambar Kode QR diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index fee5b56f346..3962f5d234b 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -640,7 +640,6 @@ Impossibile ottenere il codice PIN del dispositivo, prova l\'autenticazione locale Il codice PIN è scaduto! Il codice scadrà tra %1$dm %2$ds - hide_player_control_names_key Apri il video locale Al momento non ci sono download. Riproduci dall\'inizio diff --git a/app/src/main/res/values-b+iw/strings.xml b/app/src/main/res/values-b+iw/strings.xml index 558d98a4bc3..ef4cb9202ed 100644 --- a/app/src/main/res/values-b+iw/strings.xml +++ b/app/src/main/res/values-b+iw/strings.xml @@ -537,7 +537,6 @@ \nיגרמו לעדיפות הסרטון להיות 10. \n \nשימו לב: אם הסכום הוא 10 או יותר, הנגן ידלג על טעינת הסרטון כאשר הלינק נטען! - hide_player_control_names_key עונה %1$d פרק %2$d תשודר ב: %1$d שעות %2$d דקות %3$d שניות %1$d דקות %2$d שניות diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index df0559ef77e..e246c6f279f 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -234,7 +234,6 @@ 現在のエピソードが終了したら次のエピソードを開始する 長押しするとデフォルトにリセットされます ダウンロードを再開 - hide_player_control_names_key ブックマークのフィルタ プロットが見つかりません ダブルタップで一時停止 diff --git a/app/src/main/res/values-b+kn/strings.xml b/app/src/main/res/values-b+kn/strings.xml index 3a77aeef01d..22a45b906b2 100644 --- a/app/src/main/res/values-b+kn/strings.xml +++ b/app/src/main/res/values-b+kn/strings.xml @@ -129,5 +129,4 @@ Brightness ಅಥವಾ volume ಬದಲಾಯಿಸಲು ಎಡ ಅಥವಾ ಬಲಭಾಗದಲ್ಲಿ ಮೇಲಕ್ಕೆ ಅಥವಾ ಕೆಳಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ ಈಗಿನ ಎಪಿಸೋಡ್ ಮುಗಿದಾಗ ಮುಂದಿನ ಎಪಿಸೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ - hide_player_control_names_key diff --git a/app/src/main/res/values-b+ko/strings.xml b/app/src/main/res/values-b+ko/strings.xml index 7ab55091334..af84eb3a97e 100644 --- a/app/src/main/res/values-b+ko/strings.xml +++ b/app/src/main/res/values-b+ko/strings.xml @@ -620,7 +620,6 @@ %s의 PIN 입력 즐겨찾기에서 제거 캐스트미러 - hide_player_control_names_key 플러그인 삭제 경고 탐색바 미리보기 diff --git a/app/src/main/res/values-b+lt/strings.xml b/app/src/main/res/values-b+lt/strings.xml index 35719201893..cb2d816f382 100644 --- a/app/src/main/res/values-b+lt/strings.xml +++ b/app/src/main/res/values-b+lt/strings.xml @@ -254,5 +254,4 @@ Ar tikrai norite išeiti? Pašalinti iš žiūrimų Garso takelis - hide_player_control_names_key diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index 444a59a5ff1..b87e9e4fee9 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -509,7 +509,6 @@ Abonēto šovu atjaunināšana Abonēts Abonēts %s - hide_player_control_names_key %1$d. sezona un %2$d. sērija tiks izlaista pēc %1$dh %2$dm %3$ds %1$dm %2$ds diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index bccc2a00d44..6998c49dbec 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -593,7 +593,6 @@ Грешка при пристапот до таблата со исечоци, обиди се повторно. Грешка при копирање, молам копирај го логот и контактирај ја поддршката на апликацијата. Аудио книга - hide_player_control_names_key Безбедност Отфрли Отвори извор diff --git a/app/src/main/res/values-b+ml/strings.xml b/app/src/main/res/values-b+ml/strings.xml index dcb9e5270db..d1c9409a399 100644 --- a/app/src/main/res/values-b+ml/strings.xml +++ b/app/src/main/res/values-b+ml/strings.xml @@ -272,5 +272,4 @@ എഡ്ജ് തരം ഔട്ട്ലൈൻ നിറം പശ്ചാത്തല നിറം - hide_player_control_names_key diff --git a/app/src/main/res/values-b+ms/strings.xml b/app/src/main/res/values-b+ms/strings.xml index 8bbb2a7e055..83492a5fff3 100644 --- a/app/src/main/res/values-b+ms/strings.xml +++ b/app/src/main/res/values-b+ms/strings.xml @@ -54,7 +54,6 @@ Kongsi Tetapan Tutup - hide_player_control_names_key Pratonton Resensi:%.1f Kemas kini baru dijumpai! @@ -516,9 +515,6 @@ Sandarkan data Gagal pulihkan data dari fail %s Ralat sandaran %s - - Episode - Akan datang pada %s Ini akan memadamkan secara kekal %s\nAdakah anda pasti? Adakah anda pasti mahu memadamkan item berikut secara kekal?\n\n%s diff --git a/app/src/main/res/values-b+mt/strings.xml b/app/src/main/res/values-b+mt/strings.xml index ca62a043bfd..ea859ee293a 100644 --- a/app/src/main/res/values-b+mt/strings.xml +++ b/app/src/main/res/values-b+mt/strings.xml @@ -122,5 +122,4 @@ Bookmarks Neħħi Falla t-tniżżil - hide_player_control_names_key diff --git a/app/src/main/res/values-b+my/strings.xml b/app/src/main/res/values-b+my/strings.xml index 1d35cbaa0d0..4a7a50aa73d 100644 --- a/app/src/main/res/values-b+my/strings.xml +++ b/app/src/main/res/values-b+my/strings.xml @@ -537,5 +537,4 @@ သင်နဂိုတည်းကသတ်မှတ်ပြီး လိုက်ဘရီရွေးချယ်ရန် ဖြင့်ဖွင့်မည် - hide_player_control_names_key diff --git a/app/src/main/res/values-b+ne/strings.xml b/app/src/main/res/values-b+ne/strings.xml index 9345cab2bf6..8a432a5058d 100644 --- a/app/src/main/res/values-b+ne/strings.xml +++ b/app/src/main/res/values-b+ne/strings.xml @@ -127,5 +127,4 @@ प्लेयरको उपशीर्षकको सेटिङ रिपोजिटरी को नाम र यूआरएल कपी गरियो! - hide_player_control_names_key diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index 6e0982c79a0..54508e652e6 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -292,14 +292,6 @@ MyCoolSite voorbeeld.com Taalcode (nl) - %1$s %2$s account Log uit @@ -594,7 +586,6 @@ Link opnieuw geladen Autoroteer Roteer - hide_player_control_names_key Er zijn momenteel geen downloads beschikbaar. Gekopieerd! Verbergen diff --git a/app/src/main/res/values-b+nn/strings.xml b/app/src/main/res/values-b+nn/strings.xml index 2cf83c18350..245bf66186c 100644 --- a/app/src/main/res/values-b+nn/strings.xml +++ b/app/src/main/res/values-b+nn/strings.xml @@ -191,5 +191,4 @@ Bilde i bilde Fortsett å sjå Prøv tilkopling på nytt… - hide_player_control_names_key diff --git a/app/src/main/res/values-b+no/strings.xml b/app/src/main/res/values-b+no/strings.xml index a981609cfc5..374b033c6af 100644 --- a/app/src/main/res/values-b+no/strings.xml +++ b/app/src/main/res/values-b+no/strings.xml @@ -192,8 +192,6 @@ Primær Farge App Tema Foretrukket Videoinnhold - Disclaimer - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Besetning: %s %dm Tøm @@ -525,5 +523,4 @@ Bruk Hjelp Profilbakgrunn - hide_player_control_names_key diff --git a/app/src/main/res/values-b+or/strings.xml b/app/src/main/res/values-b+or/strings.xml index 807a3bcc69c..8c9379f5bab 100644 --- a/app/src/main/res/values-b+or/strings.xml +++ b/app/src/main/res/values-b+or/strings.xml @@ -155,7 +155,6 @@ କୌଣସି ତଥ୍ୟ ନାହିଁ %1$s ଅ %2$d ଆଦ୍ୟ ବାଦ୍ ଦିଅ - hide_player_control_names_key ଏପିସୋଡ୍ %d ମୁକ୍ତିଲାଭ କରିବ ସିଜିନ୍ %1$d ଏପିସୋଡ୍ %2$d ମୁକ୍ତିଲାଭ କରିବ %1$dଘଣ୍ଟା %2$dମିନିଟ୍ %3$dସେକେଣ୍ଡ diff --git a/app/src/main/res/values-b+pl/array.xml b/app/src/main/res/values-b+pl/array.xml index 7b1683b412f..46606685237 100644 --- a/app/src/main/res/values-b+pl/array.xml +++ b/app/src/main/res/values-b+pl/array.xml @@ -299,48 +299,4 @@ Vietnamese (VISCII) Vietnamese (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index e067b391c53..59ac0db4b63 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -621,7 +621,6 @@ Odrzuć Otwórz repozytorium Odwiedź %s na swoim smartfonie lub komputerze i wprowadź powyższy kod - hide_player_control_names_key Odtwarzaj od początku Usuń wtyczkę Uwaga diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index 3fbc4fb2830..e74a7db7485 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -266,7 +266,6 @@ Esticar Zoom Aviso Legal - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Geral Botão Aleatório Mostrar botão aleatório na página inicial e na biblioteca @@ -291,14 +290,6 @@ NovoNomedoSite https://example.com Codigo da Língua (bp) - %1$s %2$s Conta Sair @@ -640,7 +631,6 @@ Não é possível obter o código PIN do dispositivo, tente a autenticação local O código PIN expirou! O código expira em %1$dm %2$ds - hide_player_control_names_key Reproduzir do começo Reprovou alguns testes Excluir plugin diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index 23b49195ffb..e7b3623e6a9 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -606,7 +606,6 @@ Temporada %1$d Episódio %2$d será lançado em Escolha o dispositivo Transmitir - hide_player_control_names_key Abrir vídeo local Não é possível receber o PIN do dispositivo, tentando autenticação local Pré-visualização na barra de progresso diff --git a/app/src/main/res/values-b+qt/strings.xml b/app/src/main/res/values-b+qt/strings.xml index 3de0f32df58..d60a4e32c0d 100644 --- a/app/src/main/res/values-b+qt/strings.xml +++ b/app/src/main/res/values-b+qt/strings.xml @@ -239,7 +239,6 @@ oooooh uuaagh @string/home_play oouuhhh ahhooo-ahah - hide_player_control_names_key uuuugg aaaahh oogg aagg uuuuggg og %1$dm %2$ds aaaahhh ag diff --git a/app/src/main/res/values-b+ro/strings.xml b/app/src/main/res/values-b+ro/strings.xml index 642eea0c394..e2dbd0e32fa 100644 --- a/app/src/main/res/values-b+ro/strings.xml +++ b/app/src/main/res/values-b+ro/strings.xml @@ -261,7 +261,6 @@ Întindere Mărire Aviz juridic (declinarea responsabilității și drepturi de autor) - Orice probleme legale privind conținutul acestei aplicații ar trebui să fie rezolvate cu furnizorii și gazdele actuale de fișiere, întrucât noi nu suntem afiliați cu aceștia. În caz de încălcare a drepturilor de autor, vă rugăm să contactați direct părțile responsabile sau site-urile de streaming. Aplicația este destinată exclusiv utilizării educaționale și personale. CloudStream 3 nu găzduiește niciun fel de conținut în aplicație și nu are niciun control asupra conținutului media care este pus sau retras. CloudStream 3 funcționează ca orice alt motor de căutare, cum ar fi Google. CloudStream 3 nu găzduiește, nu încarcă și nu gestionează niciun videoclip, film sau conținut. Pur și simplu navighează, adună și afișează linkuri într-o interfață convenabilă și ușor de utilizat. Pur și simplu, acesta extrage paginile web ale unor terțe părți care sunt accesibile publicului prin intermediul oricărui browser web obișnuit. Este responsabilitatea utilizatorului de a evita orice acțiune care ar putea încălca legile care guvernează locația sa. Utilizați CloudStream 3 pe propria răspundere. General Aleatoriu Afișează butonul pentru aleatoriu pe Pagina Principală și în Bibliotecă @@ -627,7 +626,6 @@ Sezonul %1$d Episod %2$d va fi lansat în Selectați divece-ul pe care doriți să faceți cast Cast mirror - hide_player_control_names_key Redă de la început Nu există descărcări. Selectionati elementele de sters diff --git a/app/src/main/res/values-b+ru/strings.xml b/app/src/main/res/values-b+ru/strings.xml index d25f7e27492..d8b369a3de7 100644 --- a/app/src/main/res/values-b+ru/strings.xml +++ b/app/src/main/res/values-b+ru/strings.xml @@ -555,12 +555,10 @@ Добавить в любимое Включить автоматическую смену ориентации экрана на основе ориентации видео Автоповорот - rotate_video_key Использовать учётную запись по умолчанию Отписаться Заменить Введите текущий ПИН-код - auto_rotate_video_key Любимые %s добавлено в любимые Введите ПИН-код от %s @@ -600,7 +598,6 @@ Сезон %1$d серия %2$d выйдет Выйдет %s Выберите устройство для трансляции - hide_player_control_names_key В данный момент скачиваний нет. Играть с самого начала Открыть локальное видео diff --git a/app/src/main/res/values-b+sk/strings.xml b/app/src/main/res/values-b+sk/strings.xml index fb65841f2a2..93505971c61 100644 --- a/app/src/main/res/values-b+sk/strings.xml +++ b/app/src/main/res/values-b+sk/strings.xml @@ -369,7 +369,6 @@ Pridať repozitár Názov repozitára Zobraziť komunitné repozitáre - hide_player_control_names_key HD Prehrávač Rozlíšenie a titul diff --git a/app/src/main/res/values-b+so/strings.xml b/app/src/main/res/values-b+so/strings.xml index fc42c63f7c1..09499af0038 100644 --- a/app/src/main/res/values-b+so/strings.xml +++ b/app/src/main/res/values-b+so/strings.xml @@ -472,5 +472,4 @@ Bilowga Bilow isku qasan Qoraalka dhamaadka - hide_player_control_names_key diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml index dfbfce4b5d1..75c7efda4c4 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -465,7 +465,6 @@ Redigera konto Loggat in som %s Hoppa över val av konto vid start - auto_rotera_video_nyckel Gå förbi blockering av rå GitHub-URL:er med jsDelivr. Kan göra att uppdateringar försenas med några dagar. Funktion Önskad media @@ -557,7 +556,6 @@ %s togs bort från favoriter %s har lagts till i favoriter Använd standard konto - rotera_video_nyckel PIN-kod Sök mängden som används när spelaren är dold Det verkar som om ett potentiellt duplicerat objekt redan finns i ditt bibliotek: @@ -612,7 +610,6 @@ CloudStream Wiki Konton Säkerhet - hide_player_control_names_key Avfärda Öppna databasen Koden löper ut om %1$dm %2$ds diff --git a/app/src/main/res/values-b+ta/strings.xml b/app/src/main/res/values-b+ta/strings.xml index 94b6f717a9d..e223f6c60f0 100644 --- a/app/src/main/res/values-b+ta/strings.xml +++ b/app/src/main/res/values-b+ta/strings.xml @@ -576,7 +576,6 @@ ஆதாரங்கள் எவ்வாறு உத்தரவிடப்படுகின்றன என்பதை இங்கே மாற்றலாம். ஒரு வீடியோவுக்கு அதிக முன்னுரிமை இருந்தால், அது மூல தேர்வில் அதிகமாகத் தோன்றும். மூல முன்னுரிமையின் தொகை மற்றும் தரமான முன்னுரிமை ஆகியவை வீடியோ முன்னுரிமை. \n\n சான்று A: 3 \n தகுதி பி: 7 \n 10 இன் ஒருங்கிணைந்த வீடியோ முன்னுரிமை இருக்கும். \n\n குறிப்பு: தொகை 10 அல்லது அதற்கு மேற்பட்டதாக இருந்தால், அந்த இணைப்பு ஏற்றப்படும்போது பிளேயர் தானாகவே ஏற்றுவதைத் தவிர்க்கும்! உங்கள் கிளவுட்ச்ட்ரீம் தரவு இப்போது காப்புப் பிரதி எடுக்கப்பட்டுள்ளது. இதன் சாத்தியம் மிகக் குறைவு என்றாலும், எல்லா சாதனங்களும் வித்தியாசமாக நடந்து கொள்ளலாம். அரிய விசயத்தில், பயன்பாட்டை அணுகுவதிலிருந்து நீங்கள் பூட்டப்படுகிறீர்கள், பயன்பாட்டு தரவை முழுவதுமாக அழித்து, காப்புப்பிரதியிலிருந்து மீட்டெடுக்கவும். இதிலிருந்து எழும் ஏதேனும் சிரமத்திற்கு நாங்கள் மிகவும் வருந்துகிறோம். ஊடகம் - hide_player_control_names_key கணக்குகள் எச்சரிக்கை தற்போது பதிவிறக்கங்கள் எதுவும் இல்லை. diff --git a/app/src/main/res/values-b+ti/strings.xml b/app/src/main/res/values-b+ti/strings.xml index 6c154c8d857..46235bbd7bd 100644 --- a/app/src/main/res/values-b+ti/strings.xml +++ b/app/src/main/res/values-b+ti/strings.xml @@ -3,5 +3,4 @@ %1$s ክፋል %2$d ክፋል %d በ ላይ ይወጣል ተዋሳእቲ፡ %s - hide_player_control_names_key diff --git a/app/src/main/res/values-b+tl/strings.xml b/app/src/main/res/values-b+tl/strings.xml index 94bb8ea1d59..4050ddbd72f 100644 --- a/app/src/main/res/values-b+tl/strings.xml +++ b/app/src/main/res/values-b+tl/strings.xml @@ -257,5 +257,4 @@ Mga Subtitle ng Chromecast Mga setting ng mga subtitle ng Chromecast Maglaro ng Trailer - hide_player_control_names_key diff --git a/app/src/main/res/values-b+tr/array.xml b/app/src/main/res/values-b+tr/array.xml index 56cea4c9cd8..dbc2d3e6642 100644 --- a/app/src/main/res/values-b+tr/array.xml +++ b/app/src/main/res/values-b+tr/array.xml @@ -319,48 +319,4 @@ Vietnamese (VISCII) Vietnamese (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index f6a125b9100..10137e7b5fe 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s B. %2$d Cast: %s Bölüm %d şu tarihte yayınlanacak @@ -22,9 +13,7 @@ Bölüm Afişi Ana Afiş Sonraki Rastgele - @string/play_episode Geri git - @string/home_change_provider_img_des Sağlayıcıyı Değiştir Arkaplanı Önizle @@ -45,7 +34,6 @@ Veri Yok Daha Fazla Seçenek Sonraki bölüm - @string/synopsis Türler Paylaş Tarayıcıda aç @@ -74,7 +62,6 @@ İndirme Başarısız İndirme İptal Edildi İndirme Tamamlandı - %s - %s Ağ akışı Bağlantılar yüklenirken hata oluştu Dahili Depolama @@ -258,10 +245,6 @@ Dublaj etiketi Altyazı etiketi Başlık - show_hd_key - show_dub_key - show_sub_key - show_title_key Afiş üzerindeki öğeleri değiştir Güncelleme bulunamadı Güncellemeleri denetle @@ -293,8 +276,6 @@ Uzat Yakınlaştır Yasal uyarı - legal_notice_key - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Genel Rastgele düğmesi Ana sayfa ve kütüphane üstünde rastgele düğmesini göster @@ -314,10 +295,6 @@ Afiş başlık konumu Başlığı afişin altına yerleştir - anilist_key - mal_key - opensubtitles_key - nginx_key şifre123 Kullanıcı Adı hello@world.com @@ -325,14 +302,6 @@ YeniSiteAdı https://ornek.com Dil kodu (tr) - %1$s %2$s hesap Çıkış yap @@ -355,7 +324,6 @@ Tümü Azami Asgari - @string/none Dış hat Çökmüş Gölge @@ -614,8 +582,6 @@ PIN Geçerli PIN\'i Giriniz Ekran yönü için bir geçiş düğmesi göster - rotate_video_key - auto_rotate_video_key Otomatik döndür Döndür Video yönüne göre ekran yönünün otomatik olarak değişmesini sağla @@ -664,7 +630,6 @@ Cihaz PIN kodu alınamıyor, yerel kimlik doğrulamayı deneyin PIN kodunun süresi doldu! Kodun süresi %1$dm %2$ds içinde doluyor - hide_player_control_names_key Şu an hiç bir indirme bulunmamaktadır. Eklentiyi sil Yerel videoyu aç diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index 54562f263b1..20bd9e6e288 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -550,10 +550,8 @@ Керувати обліковими записами Редагувати обліковий запис Показувати кнопку перемикання орієнтації екрана - rotate_video_key Обернути Покликання перезавантажено - auto_rotate_video_key Автообертання Увімкнути автоматичну зміну орієнтації екрана відповідно до відео Додати налаштування швидкості до програвача @@ -600,7 +598,6 @@ Термін дії коду закінчується через %1$dхв %2$dс Локальна автентифікація Відхилити - hide_player_control_names_key Відтворити з початку Попередження Видалити розширення diff --git a/app/src/main/res/values-b+ur/strings.xml b/app/src/main/res/values-b+ur/strings.xml index d2c3d9f1c7a..5f6d8aa1473 100644 --- a/app/src/main/res/values-b+ur/strings.xml +++ b/app/src/main/res/values-b+ur/strings.xml @@ -604,7 +604,6 @@ دیگر ایکسٹینشنز میں تلاش کریں سفارشات دکھائیں آپ کے CloudStream ڈیٹا کا اب بیک اپ لیا گیا ہے۔ اگرچہ اس کا امکان بہت کم ہے، لیکن مختلف ڈیوائس مختلف طریقے سے کام کر سکتے ہیں۔ اگر آپ ایپ تک رسائی حاصل کرنے سے قاصر ہیں تو، ایپ کا ڈیٹا مکمل طور پر صاف کریں اور بیک اپ سے بحال کریں۔ اس سے ہونے والی کسی بھی تکلیف کے لیے ہم بہت معذرت خواہ ہیں۔ - hide_player_control_names_key سیزن %1$d کی قسط %2$d جاری ہوگی شروع سےپلے کریں کلام شناسی دستیاب نہیں diff --git a/app/src/main/res/values-b+vi/array.xml b/app/src/main/res/values-b+vi/array.xml index d1887505ec0..16d45e5164f 100644 --- a/app/src/main/res/values-b+vi/array.xml +++ b/app/src/main/res/values-b+vi/array.xml @@ -291,48 +291,4 @@ Vietnamese (VISCII) Vietnamese (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index aa9caffd1ac..b26c715f3a5 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -630,7 +630,6 @@ Truy cập %s trên điện thoại hoặc máy tính và nhập mã bên trên Mã PIN đã hết hạn! Mã sẽ hết hạn trong %1$dm %2$ds - hide_player_control_names_key Không lấy được mã PIN, hãy thử xác thực cục bộ Hiện không có bản tải xuống nào. Xác thực cục bộ diff --git a/app/src/main/res/values-b+zh+TW/strings.xml b/app/src/main/res/values-b+zh+TW/strings.xml index 251df543c5f..7dc4b48f20f 100644 --- a/app/src/main/res/values-b+zh+TW/strings.xml +++ b/app/src/main/res/values-b+zh+TW/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s 共 %2$d 集 演員:%s 第 %d 集即將發佈於 @@ -22,9 +13,7 @@ 劇集封面 主封面 隨機下一個 - @string/play_episode 返回 - @string/home_change_provider_img_des 更改片源 預覽背景 @@ -45,7 +34,6 @@ 無資料 更多選項 下一集 - @string/synopsis 類型 分享 在瀏覽器中打開 @@ -74,7 +62,6 @@ 下載失敗 下載取消 下載完畢 - %s - %s 網路串流 載入連結錯誤 內部儲存空間 @@ -259,10 +246,6 @@ 配音標籤 字幕標籤 標題 - show_hd_key - show_dub_key - show_sub_key - show_title_key 封面內容 未找到更新 檢查更新 @@ -294,8 +277,6 @@ 拉伸 縮放 免責聲明 - legal_notice_key - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. 通用 隨機按鈕 在主畫面與媒體庫中顯示隨機按鈕 @@ -315,10 +296,6 @@ 封面標題位置 將標題移到封面下方 - anilist_key - mal_key - opensubtitles_key - nginx_key 密碼 使用者名稱 電子郵件 @@ -326,14 +303,6 @@ 新網站名稱 https://example.com 語言代號 (zh_TW) - %1$s %2$s 帳號 登出 @@ -356,7 +325,6 @@ 全部 最大 最小 - @string/none 輪廓 凹陷 陰影 @@ -614,9 +582,7 @@ \n注意:如果加總達到 10 或更高,則載入該連結時播放器將自動跳過載入! 輸入目前的 PIN 碼 顯示切換畫面方向的按鈕 - rotate_video_key 選擇篩選外掛程式下載的模式 - auto_rotate_video_key 自動旋轉 旋轉 根據影片方向自動切換畫面方向 @@ -656,7 +622,6 @@ 為了確保下載與通知已訂閱的電視節目的不間斷,CloudStream 需要取得在背景執行的權限。若點選「確定」,將移至「應用程式資訊」,請找到「應用程式電池使用」並將電池用量設置為「無限制」。請注意,取得此權限並不表示 CS3 會明顯增加電池用量,而是只在必要時在背景執行,例如取得通知或使用官方擴充功能下載影片時。若選擇「取消」,您可以稍後在「一般設定」中調整此設定。 CloudStream Wiki 此裝置不支援生物特徵認證 - hide_player_control_names_key 無法取得裝置 PIN 碼,請嘗試本機驗證 刪除外掛程式 開啟資源庫 diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 224041e4929..0301a3a2d22 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s 共 %2$d 集 演员:%s 第 %d 集将发布于 @@ -22,9 +13,7 @@ 剧集封面 主封面 随机下一个 - @string/play_episode 返回 - @string/home_change_provider_img_des 更改片源 预览背景 @@ -45,7 +34,6 @@ 无数据 更多选项 下一集 - @string/synopsis 类型 分享 在浏览器中打开 @@ -74,7 +62,6 @@ 下载失败 下载取消 下载完毕 - %s - %s 播放 加载链接错误 内部存储 @@ -260,10 +247,6 @@ 配音标签 字幕标签 标题 - show_hd_key - show_dub_key - show_sub_key - show_title_key 封面内容 未找到更新 检查更新 @@ -295,8 +278,6 @@ 拉伸 缩放 免责声明 - legal_notice_key - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. 通用 随机按钮 在主页和库中显示随机按钮 @@ -316,10 +297,6 @@ 封面标题位置 将标题移至封面下方 - anilist_key - mal_key - opensubtitles_key - nginx_key 密码 用户名 邮箱 @@ -327,14 +304,6 @@ 网站名称 网站链接 语言代码 (zh) - %1$s %2$s 账户 注销 @@ -357,7 +326,6 @@ 全部 最大 最小 - @string/none 轮廓 凹陷 阴影 @@ -654,7 +622,6 @@ 选择投射设备 %1$d季%2$d集将在 投射镜像 - hide_player_control_names_key 目前尚无下载。 打开本地视频 安全 diff --git a/app/src/main/res/values/donottranslate-strings.xml b/app/src/main/res/values/donottranslate-strings.xml new file mode 100644 index 00000000000..5f2186fae5b --- /dev/null +++ b/app/src/main/res/values/donottranslate-strings.xml @@ -0,0 +1,152 @@ + + + + search_providers_list + app_locale + search_type_list + auto_update + auto_update_plugins + auto_download_plugins_key2 + skip_update_key + install_prerelease_key + manual_check_update + fast_forward_button_time + benene_count + subtitle_settings_key + test_providers_key + subtitle_settings_chromecast_key + quality_pref_key + quality_pref_mobile_data_key + player_default_key + prefer_limit_title_key + prefer_limit_title_rez_key + apk_installer_key + video_buffer_size_key + video_buffer_length_key + video_buffer_clear_key + video_buffer_disk_key + use_system_brightness_key + swipe_enabled_key + playback_speed_enabled_key + player_resize_enabled_key + pip_enabled_key + double_tap_enabled_key + double_tap_pause_enabled_key + double_tap_seek_time_key2 + android_tv_interface_off_seek_key + android_tv_interface_on_seek_key + swipe_vertical_enabled_key + autoplay_next_key + display_sub_key + show_fillers_key + show_trailers_key + show_kitsu_posters_key + random_button_key + provider_lang_key + dns_key + jsdelivr_proxy_key + download_path_key + download_parallel_key + download_concurrent_key + download_path_key_visual + Cloudstream + app_layout_key + primary_color_key + restore_key + backup_key + automatic_backup_key + prefer_media_type_key_2 + app_theme_key + episode_sync_enabled_key + log_enabled_key + show_logcat_key + bottom_title_key + poster_ui_key + overscan_key + poster_size_key + subtitles_encoding_key + override_site_key + redo_setup_key + filter_sub_lang_key + pref_filter_search_quality_key + enable_nsfw_on_providers_key + skip_startup_account_select_key + enable_skip_op_from_database + rotate_video_key + auto_rotate_video_key + biometric_key + battery_optimisation + show_hd_key + show_dub_key + show_sub_key + show_rating_key + show_title_key + show_episode_text_key + hide_player_control_names_key + preview_seekbar_key + backup_path_key + backup_dir_key + confirm_exit_key + software_decoding_key2 + manual_update_plugins + legal_notice_key + speedup_key + anilist_key + simkl_key + mal_key + opensubtitles_key + subdl_key + + pref_category_security_key + pref_category_gestures_key + pref_category_android_tv_key + + tv_no_focus_tag + + + %1$d %2$s | %3$s + %1$s • %2$s + %1$s - %2$s + %1$s / %2$s + %1$s %2$s + S1E1 + +%d + -%d + %d + %d + %s/10.0 + %d + + @string/play_episode + @string/home_change_provider_img_des + @string/synopsis + + @string/none + @string/none + @string/cancel + @string/cancel + @string/action_default + @string/action_default + + Any legal issues regarding the content on this application + should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream functions like any other search engine, such as Google. CloudStream does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessible via any regular web browser. It is the + responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use + CloudStream at your own risk. + + + + @string/episode + @string/episodes + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9aa586e8a29..8ad0ec42364 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,91 +1,6 @@ - - search_providers_list - app_locale - search_type_list - auto_update - auto_update_plugins - auto_download_plugins_key2 - skip_update_key - install_prerelease_key - manual_check_update - fast_forward_button_time - benene_count - subtitle_settings_key - test_providers_key - subtitle_settings_chromecast_key - quality_pref_key - quality_pref_mobile_data_key - player_default_key - prefer_limit_title_key - prefer_limit_title_rez_key - apk_installer_key - video_buffer_size_key - video_buffer_length_key - video_buffer_clear_key - video_buffer_disk_key - use_system_brightness_key - swipe_enabled_key - playback_speed_enabled_key - player_resize_enabled_key - pip_enabled_key - double_tap_enabled_key - double_tap_pause_enabled_key - double_tap_seek_time_key2 - android_tv_interface_off_seek_key - android_tv_interface_on_seek_key - swipe_vertical_enabled_key - autoplay_next_key - display_sub_key - show_fillers_key - show_trailers_key - show_kitsu_posters_key - random_button_key - provider_lang_key - dns_key - jsdelivr_proxy_key - download_path_key - download_parallel_key - download_concurrent_key - download_path_key_visual - Cloudstream - app_layout_key - primary_color_key - restore_key - backup_key - automatic_backup_key - prefer_media_type_key_2 - app_theme_key - episode_sync_enabled_key - log_enabled_key - show_logcat_key - bottom_title_key - poster_ui_key - overscan_key - poster_size_key - subtitles_encoding_key - override_site_key - redo_setup_key - filter_sub_lang_key - pref_filter_search_quality_key - enable_nsfw_on_providers_key - skip_startup_account_select_key - enable_skip_op_from_database - rotate_video_key - auto_rotate_video_key - biometric_key - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %s/10.0 - %d %1$s Ep %2$d Cast: %s Episode %d will be released in @@ -102,10 +17,8 @@ Episode Poster Main Poster Next Random - @string/play_episode Go back Play from the Beginning - @string/home_change_provider_img_des Change Provider Preview Background @@ -127,7 +40,6 @@ No Data More Options Next episode - @string/synopsis Genres Share Open In Browser @@ -139,7 +51,6 @@ Completed Dropped Plan to Watch - @string/none Rewatching Play Movie Play Trailer @@ -161,7 +72,6 @@ Download Failed Download Canceled Download Done - %s - %s Select Items to Delete There are currently no downloads. Available for watching offline @@ -188,7 +98,6 @@ Remove Set watch status Apply - @string/cancel Copy Close Clear @@ -342,8 +251,6 @@ queued No Subtitles Default - @string/action_default - @string/action_default Free Used App @@ -359,10 +266,6 @@ Livestreams NSFW Others - - @string/episode - @string/episodes - Movie Series @@ -403,12 +306,6 @@ Rating Label Title Episode Text - show_hd_key - show_dub_key - show_sub_key - show_rating_key - show_title_key - show_episode_text_key Toggle UI elements on poster No Update Found Check for Update @@ -448,23 +345,6 @@ Stretch Zoom Disclaimer - legal_notice_key - Any legal issues regarding the content on this application - should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. - - In case of copyright infringement, please directly contact the responsible parties or the streaming websites. - - The app is purely for educational and personal use. - - CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. - CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or - manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, - user-friendly interface. - - It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the - responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use - CloudStream 3 at your own risk. - ISP Bypasses Links App updates @@ -473,11 +353,8 @@ Actions Cache Android TV - pref_category_android_tv_key Gestures - pref_category_gestures_key Security - pref_category_security_key Accounts Player features Subtitles @@ -507,12 +384,6 @@ Poster title location Put the title under the poster - anilist_key - simkl_key - mal_key - opensubtitles_key - subdl_key - nginx_key password123 Username hello@world.com @@ -520,14 +391,6 @@ NewSiteName https://example.com Language code (en) - %1$s %2$s account Log out @@ -552,7 +415,6 @@ All Max Min - @string/none Outline Depressed Shadow @@ -653,7 +515,7 @@ View community repositories Public list Uppercase all subtitles - Warning: CloudStream 3 does not take any responsibility for using third-party extensions and does not provide any support for them! + Warning: CloudStream does not take any responsibility for using third-party extensions and does not provide any support for them! %s (Disabled) Tracks Audio tracks @@ -707,7 +569,6 @@ TV shows, CloudStream needs permission to run in background. By pressing "OK", you\'ll be shown a request dialog. Please press \'Allow\'.\n\nPlease note, this permission does not mean CS3 will drain your battery. It will only operate in the background when necessary, such as when receiving notifications or downloading videos from official extensions. - battery_optimisation App battery usage is already set to unrestricted Unable to open CloudStream\'s App info. Downloading app update… @@ -777,7 +638,6 @@ Add Replace Replace All - @string/cancel It appears that a potentially duplicate item already exists in your library: \'%s.\' @@ -790,7 +650,6 @@ \n\nWould you like to add this item anyway, replace the existing ones, or cancel the action? - tv_no_focus_tag Enter PIN Enter PIN for %s Enter Current PIN @@ -827,27 +686,21 @@ Code expires in %1$dm %2$ds Release Date (New to Old) Release Date (Old to New) - hide_player_control_names_key Hide names of the player\'s controls - preview_seekbar_key Seekbar preview Enable preview thumbnail on seekbar No subtitles loaded yet - backup_path_key Backup folder location - backup_dir_key Custom Confirm before exiting Show dialog before exiting the app - confirm_exit_key Show Don\'t Show Edge Size Enable torrent in Settings/Providers/Preferred media Restart app and accept Stream Torrent pop-up to proceed. - software_decoding_key2 Software decoding Software decoding enables the player to play video files not supported by your device, but may cause laggy or unstable playback on high resolution. Volume has exceeded 100% @@ -855,7 +708,6 @@ Update Plugins Update plugins manually - manual_update_plugins Starting plugin update process! Successfully updated %d plugin(s)! No plugins were updated. @@ -879,7 +731,6 @@ Overscan Changes size of posters Poster size - speedup_key LongPress Speed Toggle Hold to get 2x speed Edit Profile Image @@ -903,5 +754,4 @@ Top left Top center Top right - S1E1 From 2795e9e0e2856dfd4d43177eb0be4981da1270b3 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sun, 4 Jan 2026 10:08:15 +0100 Subject: [PATCH 410/618] feat(extractors): add vidnest extractor (#2390) --- .../lagradost/cloudstream3/extractors/AsianLoad.kt | 12 +++++++----- .../com/lagradost/cloudstream3/utils/ExtractorApi.kt | 2 ++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt index 2566816792b..70e869f552a 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt @@ -4,9 +4,12 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper -import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink -import java.net.URI + +class Vidnest : AsianLoad() { + override var name = "Vidnest" + override var mainUrl = "https://vidnest.io" +} open class AsianLoad : ExtractorApi() { override var name = "AsianLoad" @@ -20,7 +23,7 @@ open class AsianLoad : ExtractorApi() { sourceRegex.findAll(this.text).forEach { sourceMatch -> val extractedUrl = sourceMatch.groupValues[1] // Trusting this isn't mp4, may fuck up stuff - if (URI(extractedUrl).path.endsWith(".m3u8")) { + if (extractedUrl.contains(".m3u8")) { M3u8Helper.generateM3u8( name, extractedUrl, @@ -29,7 +32,7 @@ open class AsianLoad : ExtractorApi() { ).forEach { link -> extractedLinksList.add(link) } - } else if (extractedUrl.endsWith(".mp4")) { + } else if (extractedUrl.contains(".mp4")) { extractedLinksList.add( newExtractorLink( source = name, @@ -37,7 +40,6 @@ open class AsianLoad : ExtractorApi() { url = extractedUrl, ) { this.referer = url.replace(" ", "%20") - this.quality = getQualityFromName(sourceMatch.groupValues[2]) } ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 641c913195e..b9a147fc772 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -263,6 +263,7 @@ import com.lagradost.cloudstream3.extractors.Vidguardto3 import com.lagradost.cloudstream3.extractors.VidhideExtractor import com.lagradost.cloudstream3.extractors.Vidmoly import com.lagradost.cloudstream3.extractors.Vidmolyme +import com.lagradost.cloudstream3.extractors.Vidnest import com.lagradost.cloudstream3.extractors.Vido import com.lagradost.cloudstream3.extractors.Vidstreamz import com.lagradost.cloudstream3.extractors.VinovoSi @@ -1169,6 +1170,7 @@ val extractorApis: MutableList = arrayListOf( FlaswishCom(), SfastwishCom(), Playerwish(), + Vidnest(), EmturbovidExtractor(), Vtbe(), EPlayExtractor(), From dc6b9f435d988cd09c7d101eb1c043ccdb4a4e3c Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sun, 4 Jan 2026 10:09:50 +0100 Subject: [PATCH 411/618] feat(extractors): add up4stream extractor (#2389) --- .../cloudstream3/extractors/Up4Stream.kt | 60 +++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 4 ++ 2 files changed, 64 insertions(+) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt new file mode 100644 index 00000000000..91150992b3a --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt @@ -0,0 +1,60 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.api.Log +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.JsUnpacker +import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.fixUrl +import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.coroutines.delay + +class Up4FunTop : Up4Stream() { + override var mainUrl: String = "https://up4fun.top" +} + +open class Up4Stream : ExtractorApi() { + override var name = "Up4Stream" + override var mainUrl = "https://up4stream.com" + override val requiresReferer = true + + override suspend fun getUrl(url: String, referer: String?): List? { + val movieId = url.substringAfterLast("/").substringBefore(".html") + + // redirect from "wait 5 seconds" page to actual movie page + val redirectResponse = app.get(url, cookies = mapOf("id" to movieId)) + val redirectForm = redirectResponse.document.selectFirst("form[method=POST]") ?: return null + val redirectUrl = fixUrl(redirectForm.attr("action")) + val redirectParams = redirectForm.select("input[type=hidden]").associate { input -> + input.attr("name") to input.attr("value") + } + + // wait for 5 seconds, otherwise the below md5 hash is invalid + delay(5000) + val response = app.post(redirectUrl, data = redirectParams).document + + // starting here, this works similar to many other extractors like StreamWish + val extractedpack = + response.selectFirst("script:containsData(function(p,a,c,k,e,d))")?.data() + if (extractedpack == null) { + Log.e("up4stream", "file not ready: delay too short") + } + + JsUnpacker(extractedpack).unpack()?.let { unPacked -> + Regex("sources:\\[\\{file:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link -> + return listOf( + newExtractorLink( + this.name, + this.name, + link, + ) { + this.referer = referer.orEmpty() + this.quality = Qualities.Unknown.value + } + ) + } + } + return null + } +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index b9a147fc772..c0d5c5da700 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -230,6 +230,8 @@ import com.lagradost.cloudstream3.extractors.Techinmind import com.lagradost.cloudstream3.extractors.Tomatomatela import com.lagradost.cloudstream3.extractors.TomatomatelalClub import com.lagradost.cloudstream3.extractors.Tubeless +import com.lagradost.cloudstream3.extractors.Up4FunTop +import com.lagradost.cloudstream3.extractors.Up4Stream import com.lagradost.cloudstream3.extractors.Upstream import com.lagradost.cloudstream3.extractors.UpstreamExtractor import com.lagradost.cloudstream3.extractors.Uqload @@ -1223,6 +1225,8 @@ val extractorApis: MutableList = arrayListOf( VkExtractor(), Bysezejataos(), ByseSX(), + Up4Stream(), + Up4FunTop() ) From 5e54552338eca91ec7b792e54dd0e5da28d17bf5 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Sun, 4 Jan 2026 15:59:04 +0530 Subject: [PATCH 412/618] remove check icon in tvtype chips (#2363) --- app/src/main/res/values/styles.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index eb5a57c03d5..8cf61eaea96 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -102,6 +102,7 @@ @font/google_sans @string/tv_no_focus_tag 0dp + false + + +