From 91be244d616455fe6f857508f1eaed631630f6a4 Mon Sep 17 00:00:00 2001 From: LagradOst Date: Sat, 5 Feb 2022 23:21:45 +0100 Subject: [PATCH] cast + minify (json fix) + ongoing + UI change --- app/build.gradle | 14 ++- .../com/lagradost/cloudstream3/MainAPI.kt | 79 ++++++++---- .../animeproviders/GogoanimeProvider.kt | 13 +- .../animeproviders/ZoroProvider.kt | 25 ++++ .../metaproviders/TmdbProvider.kt | 118 +++++++++++------- .../movieproviders/AllMoviesForYouProvider.kt | 1 + .../movieproviders/MeloMovieProvider.kt | 34 +++-- .../movieproviders/PelisflixProvider.kt | 50 +++++--- .../movieproviders/SeriesflixProvider.kt | 40 +++--- .../movieproviders/SflixProvider.kt | 34 ++++- .../cloudstream3/ui/home/HomeFragment.kt | 4 +- .../cloudstream3/ui/result/ActorAdaptor.kt | 111 ++++++++++++++++ .../cloudstream3/ui/result/ResultFragment.kt | 71 +++++++++-- .../cloudstream3/utils/DataStoreHelper.kt | 56 +++++---- .../cloudstream3/utils/VideoDownloadHelper.kt | 45 +++---- .../utils/VideoDownloadManager.kt | 46 +++---- .../cloudstream3/widget/FlowLayout.kt | 21 +++- app/src/main/res/layout/cast_item.xml | 107 ++++++++++++++++ app/src/main/res/layout/fragment_home.xml | 2 + app/src/main/res/layout/fragment_result.xml | 79 ++++++------ app/src/main/res/values/attrs.xml | 32 ++--- app/src/main/res/values/strings.xml | 4 + app/src/main/res/values/styles.xml | 13 ++ app/src/main/res/xml/settings.xml | 2 +- 24 files changed, 731 insertions(+), 270 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt create mode 100644 app/src/main/res/layout/cast_item.xml diff --git a/app/build.gradle b/app/build.gradle index a71a48b5..e39d03f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,7 +36,7 @@ android { targetSdkVersion 30 versionCode 42 - versionName "2.6.9" + versionName "2.6.10" resValue "string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}" @@ -52,8 +52,9 @@ android { buildTypes { release { - minifyEnabled false - debuggable true + debuggable false + minifyEnabled true + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } prerelease { @@ -61,12 +62,15 @@ android { buildConfigField("boolean", "BETA", "true") signingConfig signingConfigs.prerelease versionNameSuffix '-PRE' - minifyEnabled false - debuggable true + minifyEnabled true + debuggable false + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { + debuggable true applicationIdSuffix ".debug" + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index ccd76adb..437e3267 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -189,12 +189,13 @@ object APIHolder { return realSet } - fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired : Boolean = true): List { + fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) val currentPrefMedia = settingsManager.getInt(this.getString(R.string.prefer_media_type_key), 0) val langs = this.getApiProviderLangSettings() - val allApis = apis.filter { langs.contains(it.lang) }.filter { api -> api.hasMainPage || !hasHomePageIsRequired} + val allApis = apis.filter { langs.contains(it.lang) } + .filter { api -> api.hasMainPage || !hasHomePageIsRequired } return if (currentPrefMedia < 1) { allApis } else { @@ -426,6 +427,23 @@ interface SearchResponse { val id: Int? } +enum class ActorRole { + Main, + Supporting, +} + +data class Actor( + val name: String, + val image: String? = null, +) + +data class ActorData( + val actor: Actor, + val role: ActorRole? = null, + val roleString : String? = null, + val voiceActor: Actor? = null, +) + data class AnimeSearchResponse( override val name: String, override val url: String, @@ -488,6 +506,38 @@ interface LoadResponse { var duration: Int? // in minutes val trailerUrl: String? val recommendations: List? + var actors: List? + + companion object { + fun LoadResponse.setActorNames(actors: List?) { + this.actors = actors?.map { ActorData(Actor(it)) } + } + + fun LoadResponse.setActors(actors: List>?) { + println("ACTORS: ${actors?.size}") + this.actors = actors?.map { (actor, role) -> ActorData(actor, roleString = role) } + } + + fun LoadResponse.setDuration(input: String?) { + val cleanInput = input?.trim()?.replace(" ","") ?: return + Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values -> + if (values.size == 3) { + val hours = values[1].toIntOrNull() + val minutes = values[2].toIntOrNull() + this.duration = if (minutes != null && hours != null) { + hours * 60 + minutes + } else null + if (this.duration != null) return + } + } + Regex("([0-9]*)m").find(cleanInput)?.groupValues?.let { values -> + if (values.size == 2) { + this.duration = values[1].toIntOrNull() + if (this.duration != null) return + } + } + } + } } fun LoadResponse?.isEpisodeBased(): Boolean { @@ -530,6 +580,7 @@ data class TorrentLoadResponse( override var duration: Int? = null, override var trailerUrl: String? = null, override var recommendations: List? = null, + override var actors: List? = null, ) : LoadResponse data class AnimeLoadResponse( @@ -556,6 +607,7 @@ data class AnimeLoadResponse( override var duration: Int? = null, override var trailerUrl: String? = null, override var recommendations: List? = null, + override var actors: List? = null, ) : LoadResponse fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List?) { @@ -591,6 +643,7 @@ data class MovieLoadResponse( override var duration: Int? = null, override var trailerUrl: String? = null, override var recommendations: List? = null, + override var actors: List? = null, ) : LoadResponse fun MainAPI.newMovieLoadResponse( @@ -611,24 +664,6 @@ fun MainAPI.newMovieLoadResponse( return builder } -fun LoadResponse.setDuration(input: String?) { - if (input == null) return - Regex("([0-9]*)h.*?([0-9]*)m").matchEntire(input)?.groupValues?.let { values -> - if (values.size == 3) { - val hours = values[1].toIntOrNull() - val minutes = values[2].toIntOrNull() - this.duration = if (minutes != null && hours != null) { - hours * 60 + minutes - } else null - } - } - Regex("([0-9]*)m").matchEntire(input)?.groupValues?.let { values -> - if (values.size == 2) { - this.duration = values[1].toIntOrNull() - } - } -} - data class TvSeriesEpisode( val name: String? = null, val season: Int? = null, @@ -658,6 +693,7 @@ data class TvSeriesLoadResponse( override var duration: Int? = null, override var trailerUrl: String? = null, override var recommendations: List? = null, + override var actors: List? = null, ) : LoadResponse fun MainAPI.newTvSeriesLoadResponse( @@ -682,6 +718,7 @@ fun fetchUrls(text: String?): List { if (text.isNullOrEmpty()) { return listOf() } - val linkRegex = Regex("""(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*))""") + val linkRegex = + Regex("""(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*))""") return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt index b4837439..95d636a5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/GogoanimeProvider.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.animeproviders +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.ExtractorLink @@ -218,18 +219,18 @@ class GogoanimeProvider : MainAPI() { } data class GogoSources( - val source: List?, - val sourceBk: List?, + @JsonProperty("source") val source: List?, + @JsonProperty("sourceBk") val sourceBk: List?, //val track: List, //val advertising: List, //val linkiframe: String ) data class GogoSource( - val file: String, - val label: String?, - val type: String?, - val default: String? = null + @JsonProperty("file") val file: String, + @JsonProperty("label") val label: String?, + @JsonProperty("type") val type: String?, + @JsonProperty("default") val default: String? = null ) private suspend fun extractVideos(uri: String, callback: (ExtractorLink) -> Unit) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt index a437e509..53f980ef 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/ZoroProvider.kt @@ -172,6 +172,13 @@ class ZoroProvider : MainAPI() { } } + private fun Element?.getActor(): Actor? { + val image = + fixUrlNull(this?.selectFirst(".pi-avatar > img")?.attr("data-src")) ?: return null + val name = this?.selectFirst(".pi-detail > .pi-name")?.text() ?: return null + return Actor(name = name, image = image) + } + override suspend fun load(url: String): LoadResponse { val html = app.get(url).text val document = Jsoup.parse(html) @@ -222,6 +229,23 @@ class ZoroProvider : MainAPI() { ) } + val actors = document.select("div.block-actors-content > div.bac-list-wrap > div.bac-item") + ?.mapNotNull { head -> + val subItems = head.select(".per-info") ?: return@mapNotNull null + if(subItems.isEmpty()) return@mapNotNull null + var role: ActorRole? = null + val mainActor = subItems.first()?.let { + role = when (it.selectFirst(".pi-detail > .pi-cast")?.text()?.trim()) { + "Supporting" -> ActorRole.Supporting + "Main" -> ActorRole.Main + else -> null + } + it.getActor() + } ?: return@mapNotNull null + val voiceActor = if(subItems.size >= 2) subItems[1]?.getActor() else null + ActorData(actor = mainActor, role = role, voiceActor = voiceActor) + } + val recommendations = document.select("#main-content > section > .tab-content > div > .film_list-wrap > .flw-item") .mapNotNull { head -> @@ -254,6 +278,7 @@ class ZoroProvider : MainAPI() { plot = description this.tags = tags this.recommendations = recommendations + this.actors = actors } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt index a2bebe8f..7d5d8193 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.metaproviders import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.setActors import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.uwetrottmann.tmdb2.Tmdb import com.uwetrottmann.tmdb2.entities.* @@ -79,6 +80,15 @@ open class TmdbProvider : MainAPI() { ) } + private fun List?.toActors(): List>? { + return this?.mapNotNull { + Pair( + Actor(it?.name ?: return@mapNotNull null, getImageUrl(it.profile_path)), + it.character + ) + } + } + private fun TvShow.toLoadResponse(): TvSeriesLoadResponse { val episodes = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 } ?.mapNotNull { season -> @@ -112,57 +122,56 @@ open class TmdbProvider : MainAPI() { } }?.flatten() ?: listOf() - return TvSeriesLoadResponse( + return newTvSeriesLoadResponse( this.name ?: this.original_name, getUrl(id, true), - this@TmdbProvider.apiName, TvType.TvSeries, - episodes, - getImageUrl(this.poster_path), - this.first_air_date?.let { + episodes + ) { + posterUrl = getImageUrl(poster_path) + year = first_air_date?.let { Calendar.getInstance().apply { time = it }.get(Calendar.YEAR) - }, - this.overview, - null, // this.status - this.external_ids?.imdb_id, - this.rating, - this.genres?.mapNotNull { it.name }, - this.episode_run_time?.average()?.toInt(), - null, - (this.recommendations ?: this.similar)?.results?.map { it.toSearchResponse() } - ) + } + plot = overview + imdbId = external_ids?.imdb_id + tags = genres?.mapNotNull { it.name } + duration = episode_run_time?.average()?.toInt() + rating = this@toLoadResponse.rating + + recommendations = (this@toLoadResponse.recommendations + ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } + setActors(credits?.cast?.toList().toActors()) + } } private fun Movie.toLoadResponse(): MovieLoadResponse { - println("TRAILRES::::::: ${this.similar} :::: ${this.recommendations} ") - return MovieLoadResponse( - this.title ?: this.original_title, - getUrl(id, false), - this@TmdbProvider.apiName, - TvType.Movie, - TmdbLink( + return newMovieLoadResponse( + this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink( this.imdb_id, this.id, null, null, this.title ?: this.original_title, - ).toJson(), - getImageUrl(this.poster_path), - this.release_date?.let { + ).toJson() + ) { + posterUrl = getImageUrl(poster_path) + year = release_date?.let { Calendar.getInstance().apply { time = it }.get(Calendar.YEAR) - }, - this.overview, - null,//this.status - this.rating, - this.genres?.mapNotNull { it.name }, - this.runtime, - null, - (this.recommendations ?: this.similar)?.results?.map { it.toSearchResponse() } - ) + } + plot = overview + imdbId = external_ids?.imdb_id + tags = genres?.mapNotNull { it.name } + duration = runtime + rating = this@toLoadResponse.rating + + recommendations = (this@toLoadResponse.recommendations + ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } + setActors(credits?.cast?.toList().toActors()) + } } override suspend fun getMainPage(): HomePageResponse { @@ -248,23 +257,38 @@ open class TmdbProvider : MainAPI() { return if (isTvSeries) { val body = tmdb.tvService().tv(id, "en-US").awaitResponse().body() val response = body?.toLoadResponse() - if (response != null && response.recommendations.isNullOrEmpty()) { - tmdb.tvService().recommendations(id, 1,"en-US").awaitResponse().body()?.let { - it.results?.map { res -> res.toSearchResponse() } - }?.let { list -> - response.recommendations = list - } + if (response != null) { + if (response.recommendations.isNullOrEmpty()) + tmdb.tvService().recommendations(id, 1, "en-US").awaitResponse().body() + ?.let { + it.results?.map { res -> res.toSearchResponse() } + }?.let { list -> + response.recommendations = list + } + + if (response.actors.isNullOrEmpty()) + tmdb.tvService().credits(id, "en-US").awaitResponse().body()?.let { + response.setActors(it.cast?.toActors()) + } } + response } else { val body = tmdb.moviesService().summary(id, "en-US").awaitResponse().body() val response = body?.toLoadResponse() - if (response != null && response.recommendations.isNullOrEmpty()) { - tmdb.moviesService().recommendations(id, 1,"en-US").awaitResponse().body()?.let { - it.results?.map { res -> res.toSearchResponse() } - }?.let { list -> - response.recommendations = list - } + if (response != null) { + if (response.recommendations.isNullOrEmpty()) + tmdb.moviesService().recommendations(id, 1, "en-US").awaitResponse().body() + ?.let { + it.results?.map { res -> res.toSearchResponse() } + }?.let { list -> + response.recommendations = list + } + + if (response.actors.isNullOrEmpty()) + tmdb.moviesService().credits(id).awaitResponse().body()?.let { + response.setActors(it.cast?.toActors()) + } } response } @@ -296,4 +320,4 @@ open class TmdbProvider : MainAPI() { it.movie?.toSearchResponse() ?: it.tvShow?.toSearchResponse() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AllMoviesForYouProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AllMoviesForYouProvider.kt index 9d610039..5e0f538f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AllMoviesForYouProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/AllMoviesForYouProvider.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.movieproviders import com.fasterxml.jackson.module.kotlin.readValue import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt index b13be122..061811ea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt @@ -26,7 +26,10 @@ class MeloMovieProvider : MainAPI() { //"mppa" for tags ) - data class MeloMovieLink(val name: String, val link: String) + data class MeloMovieLink( + @JsonProperty("name") val name: String, + @JsonProperty("link") val link: String + ) override suspend fun quickSearch(query: String): List { return search(query) @@ -106,7 +109,16 @@ class MeloMovieProvider : MainAPI() { ): Boolean { val links = parseJson>(data) for (link in links) { - callback.invoke(ExtractorLink(this.name, link.name, link.link, "", getQualityFromName(link.name), false)) + callback.invoke( + ExtractorLink( + this.name, + link.name, + link.link, + "", + getQualityFromName(link.name), + false + ) + ) } return true } @@ -125,11 +137,13 @@ class MeloMovieProvider : MainAPI() { val type = findUsingRegex("var posttype = ([0-9]*)")?.toInt() ?: return null val titleInfo = document.selectFirst("div.movie_detail_title > div > div > h1") val title = titleInfo.ownText() - val year = titleInfo.selectFirst("> a")?.text()?.replace("(", "")?.replace(")", "")?.toIntOrNull() + val year = + titleInfo.selectFirst("> a")?.text()?.replace("(", "")?.replace(")", "")?.toIntOrNull() val plot = document.selectFirst("div.col-lg-12 > p").text() if (type == 1) { // MOVIE - val serialize = document.selectFirst("table.accordion__list") ?: throw ErrorLoadingException("No links found") + val serialize = document.selectFirst("table.accordion__list") + ?: throw ErrorLoadingException("No links found") return MovieLoadResponse( title, url, @@ -143,15 +157,19 @@ class MeloMovieProvider : MainAPI() { ) } else if (type == 2) { val episodes = ArrayList() - val seasons = document.select("div.accordion__card") ?: throw ErrorLoadingException("No episodes found") + val seasons = document.select("div.accordion__card") + ?: throw ErrorLoadingException("No episodes found") for (s in seasons) { val season = - s.selectFirst("> div.card-header > button > span").text().replace("Season: ", "").toIntOrNull() + s.selectFirst("> div.card-header > button > span").text() + .replace("Season: ", "").toIntOrNull() val localEpisodes = s.select("> div.collapse > div > div > div.accordion__card") for (e in localEpisodes) { val episode = - e.selectFirst("> div.card-header > button > span").text().replace("Episode: ", "").toIntOrNull() - val links = e.selectFirst("> div.collapse > div > table.accordion__list") ?: continue + e.selectFirst("> div.card-header > button > span").text() + .replace("Episode: ", "").toIntOrNull() + val links = + e.selectFirst("> div.collapse > div > table.accordion__list") ?: continue val data = serializeData(links) episodes.add(TvSeriesEpisode(null, season, episode, data)) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisflixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisflixProvider.kt index 33d86065..77254e0c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisflixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/PelisflixProvider.kt @@ -1,11 +1,12 @@ package com.lagradost.cloudstream3.movieproviders import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.utils.* -import kotlin.collections.ArrayList +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor -class PelisflixProvider:MainAPI() { +class PelisflixProvider : MainAPI() { override val mainUrl = "https://pelisflix.li" override val name = "Pelisflix" override val lang = "es" @@ -16,6 +17,7 @@ class PelisflixProvider:MainAPI() { TvType.Movie, TvType.TvSeries, ) + override suspend fun getMainPage(): HomePageResponse { val items = ArrayList() val urls = listOf( @@ -41,12 +43,13 @@ class PelisflixProvider:MainAPI() { items.add(HomePageList(i.second, home)) } catch (e: Exception) { - logError(e) + logError(e) } } if (items.size <= 0) throw ErrorLoadingException() return HomePageResponse(items) } + override suspend fun search(query: String): List { val url = "$mainUrl/?s=$query" val doc = app.get(url).document @@ -93,17 +96,21 @@ class PelisflixProvider:MainAPI() { .replace(descRegex2, "").replace(descRegex3, "") .replace(descRegex4, "").replace(descRegex5, "") val desc2Regex = Regex("(G(e|é)nero:.*..)") - val descipt2 = document.selectFirst("div.Description").text().replace(desc2Regex,"") + val descipt2 = document.selectFirst("div.Description").text().replace(desc2Regex, "") val rating = - document.selectFirst("div.rating-content button.like-mov span.vot_cl")?.text()?.toFloatOrNull() + document.selectFirst("div.rating-content button.like-mov span.vot_cl")?.text() + ?.toFloatOrNull() ?.times(0)?.toInt() val year = document.selectFirst("span.Date")?.text() - val duration = if (type == TvType.Movie) document.selectFirst(".Container .Container span.Time").text() else null + val duration = + if (type == TvType.Movie) document.selectFirst(".Container .Container span.Time") + .text() else null val postercss = document.selectFirst("head").toString() - val posterRegex = Regex("(\"og:image\" content=\"https:\\/\\/seriesflix.video\\/wp-content\\/uploads\\/(\\d+)\\/(\\d+)\\/?.*.jpg)") + val posterRegex = + Regex("(\"og:image\" content=\"https:\\/\\/seriesflix.video\\/wp-content\\/uploads\\/(\\d+)\\/(\\d+)\\/?.*.jpg)") val poster = try { posterRegex.findAll(postercss).map { - it.value.replace("\"og:image\" content=\"","") + it.value.replace("\"og:image\" content=\"", "") }.toList().first() } catch (e: Exception) { document.select(".TPostBg").attr("src") @@ -122,8 +129,8 @@ class PelisflixProvider:MainAPI() { val episodeList = ArrayList() - for (season in list) { - val seasonDocument = app.get(season.second).document + for ((seasonInt, seasonUrl) in list) { + val seasonDocument = app.get(seasonUrl).document val episodes = seasonDocument.select("table > tbody > tr") if (episodes.isNotEmpty()) { episodes.forEach { episode -> @@ -136,7 +143,7 @@ class PelisflixProvider:MainAPI() { episodeList.add( TvSeriesEpisode( name, - season.first, + seasonInt, epNum, href, fixUrlNull(epthumb), @@ -160,7 +167,6 @@ class PelisflixProvider:MainAPI() { rating ) } else { - return newMovieLoadResponse( title, url, @@ -186,14 +192,17 @@ class PelisflixProvider:MainAPI() { val movieID = it.attr("data-id") val serverID = it.attr("data-key") val type = if (data.contains("pelicula")) 1 else 2 - val url = "$mainUrl/?trembed=$serverID&trid=$movieID&trtype=$type" //This is to get the POST key value + val url = + "$mainUrl/?trembed=$serverID&trid=$movieID&trtype=$type" //This is to get the POST key value val doc1 = app.get(url).document doc1.select("div.Video iframe").apmap { val iframe = it.attr("src") - val postkey = iframe.replace("/stream/index.php?h=","") // this obtains + val postkey = iframe.replace("/stream/index.php?h=", "") // this obtains // djNIdHNCR2lKTGpnc3YwK3pyRCs3L2xkQmljSUZ4ai9ibTcza0JRODNMcmFIZ0hPejdlYW0yanJIL2prQ1JCZA POST KEY - app.post("https://pelisflix.li/stream/r.php", - headers = mapOf("Host" to "pelisflix.li", + app.post( + "https://pelisflix.li/stream/r.php", + headers = mapOf( + "Host" to "pelisflix.li", "User-Agent" to USER_AGENT, "Accept" to "ext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language" to "en-US,en;q=0.5", @@ -208,12 +217,13 @@ class PelisflixProvider:MainAPI() { "Sec-Fetch-User" to "?1", "Pragma" to "no-cache", "Cache-Control" to "no-cache", - "TE" to "trailers"), + "TE" to "trailers" + ), params = mapOf(Pair("h", postkey)), - data = mapOf(Pair("h", postkey)), + data = mapOf(Pair("h", postkey)), allowRedirects = false ).response.headers.values("location").apmap { link -> - val url1 = link.replace("#bu","") + val url1 = link.replace("#bu", "") loadExtractor(url1, data, callback) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SeriesflixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SeriesflixProvider.kt index f2b71a90..798c02d0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SeriesflixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SeriesflixProvider.kt @@ -1,11 +1,12 @@ package com.lagradost.cloudstream3.movieproviders import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.utils.* -import kotlin.collections.ArrayList +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.loadExtractor -class SeriesflixProvider:MainAPI() { +class SeriesflixProvider : MainAPI() { override val mainUrl = "https://seriesflix.video" override val name = "Seriesflix" override val lang = "es" @@ -16,6 +17,7 @@ class SeriesflixProvider:MainAPI() { TvType.Movie, TvType.TvSeries, ) + override suspend fun getMainPage(): HomePageResponse { val items = ArrayList() val urls = listOf( @@ -48,6 +50,7 @@ class SeriesflixProvider:MainAPI() { if (items.size <= 0) throw ErrorLoadingException() return HomePageResponse(items) } + override suspend fun search(query: String): List { val url = "$mainUrl/?s=$query" val doc = app.get(url).document @@ -80,15 +83,14 @@ class SeriesflixProvider:MainAPI() { } - - override suspend fun load(url: String): LoadResponse? { + override suspend fun load(url: String): LoadResponse { val type = if (url.contains("/movies/")) TvType.Movie else TvType.TvSeries val document = app.get(url).document val title = document.selectFirst("h1.Title").text() val descRegex = Regex("(Recuerda.*Seriesflix.)") - val descipt = document.selectFirst("div.Description > p").text().replace(descRegex,"") + val descipt = document.selectFirst("div.Description > p").text().replace(descRegex, "") val rating = document.selectFirst("div.Vote > div.post-ratings > span")?.text()?.toFloatOrNull() ?.times(1000)?.toInt() @@ -100,10 +102,11 @@ class SeriesflixProvider:MainAPI() { null } val postercss = document.selectFirst("head").toString() - val posterRegex = Regex("(\"og:image\" content=\"https:\\/\\/seriesflix.video\\/wp-content\\/uploads\\/(\\d+)\\/(\\d+)\\/?.*.jpg)") + val posterRegex = + Regex("(\"og:image\" content=\"https://seriesflix.video/wp-content/uploads/(\\d+)/(\\d+)/?.*.jpg)") val poster = try { posterRegex.findAll(postercss).map { - it.value.replace("\"og:image\" content=\"","") + it.value.replace("\"og:image\" content=\"", "") }.toList().first() } catch (e: Exception) { document.select(".TPostBg").attr("src") @@ -186,14 +189,18 @@ class SeriesflixProvider:MainAPI() { val movieID = it.attr("data-id") val serverID = it.attr("data-key") val type = if (data.contains("movies")) 1 else 2 - val url = "$mainUrl/?trembed=$serverID&trid=$movieID&trtype=$type" //This is to get the POST key value + val url = + "$mainUrl/?trembed=$serverID&trid=$movieID&trtype=$type" //This is to get the POST key value val doc1 = app.get(url).document doc1.select("div.Video iframe").apmap { val iframe = it.attr("src") - val postkey = iframe.replace("https://sc.seriesflix.video/index.php?h=","") // this obtains + val postkey = + iframe.replace("https://sc.seriesflix.video/index.php?h=", "") // this obtains // djNIdHNCR2lKTGpnc3YwK3pyRCs3L2xkQmljSUZ4ai9ibTcza0JRODNMcmFIZ0hPejdlYW0yanJIL2prQ1JCZA POST KEY - app.post("https://sc.seriesflix.video/r.php", - headers = mapOf("Host" to "sc.seriesflix.video", + app.post( + "https://sc.seriesflix.video/r.php", + headers = mapOf( + "Host" to "sc.seriesflix.video", "User-Agent" to USER_AGENT, "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language" to "en-US,en;q=0.5", @@ -206,12 +213,13 @@ class SeriesflixProvider:MainAPI() { "Sec-Fetch-Dest" to "iframe", "Sec-Fetch-Mode" to "navigate", "Sec-Fetch-Site" to "same-origin", - "Sec-Fetch-User" to "?1",), + "Sec-Fetch-User" to "?1", + ), params = mapOf(Pair("h", postkey)), - data = mapOf(Pair("h", postkey)), + data = mapOf(Pair("h", postkey)), allowRedirects = false - ).response.headers.values("location").apmap {link -> - val url1 = link.replace("#bu","") + ).response.headers.values("location").apmap { link -> + val url1 = link.replace("#bu", "") loadExtractor(url1, data, callback) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt index 51f8461d..624ccdc8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/SflixProvider.kt @@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.movieproviders import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.LoadResponse.Companion.setActorNames +import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration import com.lagradost.cloudstream3.network.WebViewResolver import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson @@ -100,13 +102,37 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { val img = details.select("img.film-poster-img") val posterUrl = img.attr("src") val title = img.attr("title") + + /* val year = Regex("""[Rr]eleased:\s*(\d{4})""").find( document.select("div.elements").text() )?.groupValues?.get(1)?.toIntOrNull() val duration = Regex("""[Dd]uration:\s*(\d*)""").find( document.select("div.elements").text() - )?.groupValues?.get(1)?.trim()?.plus(" min") - + )?.groupValues?.get(1)?.trim()?.plus(" min")*/ + var duration = document.selectFirst(".fs-item > .duration").text()?.trim() + var year: Int? = null + var tags: List? = null + var cast: List? = null + document.select("div.elements > .row > div > .row-line")?.forEach { element -> + val type = element?.select(".type")?.text() ?: return@forEach + when { + type.contains("Released") -> { + year = Regex("\\d+").find( + element.ownText() ?: return@forEach + )?.groupValues?.firstOrNull()?.toIntOrNull() + } + type.contains("Genre") -> { + tags = element.select("a")?.mapNotNull { it.text() } + } + type.contains("Cast") -> { + cast = element.select("a")?.mapNotNull { it.text() } + } + type.contains("Duration") -> { + duration = duration ?: element.ownText()?.trim() + } + } + } val plot = details.select("div.description").text().replace("Overview:", "").trim() val isMovie = url.contains("/movie/") @@ -156,6 +182,8 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { this.posterUrl = posterUrl this.plot = plot setDuration(duration) + setActorNames(cast) + this.tags = tags this.recommendations = recommendations } } else { @@ -201,6 +229,8 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { this.year = year this.plot = plot setDuration(duration) + setActorNames(cast) + this.tags = tags this.recommendations = recommendations } } 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 f794a2f4..3d797c05 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 @@ -779,10 +779,10 @@ class HomeFragment : Fragment() { home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { view, _, scrollY, _, oldScrollY -> val dy = scrollY - oldScrollY if (dy > 0) { //check for scroll down - home_api_fab?.hide() + home_api_fab?.shrink() // hide } else if (dy < -5) { if (view?.context?.isTvSettings() == false) { - home_api_fab?.show() + home_api_fab?.extend() // show } } }) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt new file mode 100644 index 00000000..85ea8619 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt @@ -0,0 +1,111 @@ +package com.lagradost.cloudstream3.ui.result + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.ActorData +import com.lagradost.cloudstream3.ActorRole +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.UIHelper.setImage +import kotlinx.android.synthetic.main.cast_item.view.* + +class ActorAdaptor( + private val actors: MutableList, +) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return CardViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.cast_item, parent, false), + ) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is CardViewHolder -> { + holder.bind(actors[position]) + } + } + } + + override fun getItemCount(): Int { + return actors.size + } + + fun updateList(newList: List) { + val diffResult = DiffUtil.calculateDiff( + ActorDiffCallback(this.actors, newList) + ) + + actors.clear() + actors.addAll(newList) + + diffResult.dispatchUpdatesTo(this) + } + + private class CardViewHolder + constructor( + itemView: View, + ) : + RecyclerView.ViewHolder(itemView) { + private val actorImage: ImageView = itemView.actor_image + private val actorName: TextView = itemView.actor_name + private val actorExtra: TextView = itemView.actor_extra + private val voiceActorImage: ImageView = itemView.voice_actor_image + private val voiceActorImageHolder: View = itemView.voice_actor_image_holder + private val voiceActorName: TextView = itemView.voice_actor_name + + fun bind(card: ActorData) { + actorImage.setImage(card.actor.image) + actorName.text = card.actor.name + card.role?.let { + actorExtra.context?.getString( + when (it) { + ActorRole.Main -> { + R.string.actor_main + } + ActorRole.Supporting -> { + R.string.actor_supporting + } + } + )?.let { text -> + actorExtra.isVisible = true + actorExtra.text = text + } + } ?: card.roleString?.let { + actorExtra.isVisible = true + actorExtra.text = it + } ?: run { + actorExtra.isVisible = false + } + + if (card.voiceActor == null) { + voiceActorImageHolder.isVisible = false + voiceActorName.isVisible = false + } else { + voiceActorName.text = card.voiceActor.name + voiceActorImageHolder.isVisible = voiceActorImage.setImage(card.voiceActor.image) + } + } + } +} + +class ActorDiffCallback( + private val oldList: List, + private val newList: List +) : + DiffUtil.Callback() { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition].actor.name == newList[newItemPosition].actor.name + + override fun getOldListSize() = oldList.size + + override fun getNewListSize() = newList.size + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] +} \ No newline at end of file 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 7e831f98..95c460df 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 @@ -391,6 +391,10 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio //requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR downloadButton?.dispose() updateUIListener = null + result_cast_items?.let { + PanelsChildGestureRegionObserver.Provider.get().unregister(it) + } + super.onDestroy() } @@ -482,6 +486,22 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio setFormatText(result_meta_duration, R.string.duration_format, duration) } + private fun setShow(showStatus : ShowStatus?) { + val status = when (showStatus) { + null -> null + ShowStatus.Ongoing -> R.string.status_ongoing + ShowStatus.Completed -> R.string.status_completed + } + + if (status == null) { + result_meta_status?.isVisible = false + } else { + context?.getString(status)?.let { + result_meta_status?.text = it + } + } + } + private fun setYear(year: Int?) { setFormatText(result_meta_year, R.string.year_format, year) } @@ -490,6 +510,27 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f)) } + private fun setActors(actors: List?) { + if (actors.isNullOrEmpty()) { + result_cast_text?.isVisible = false + result_cast_items?.isVisible = false + } else { + val isImage = actors.first().actor.image != null + if (isImage) { + (result_cast_items?.adapter as ActorAdaptor?)?.apply { + updateList(actors) + } + result_cast_text?.isVisible = false + result_cast_items?.isVisible = true + } else { + result_cast_text?.isVisible = true + result_cast_items?.isVisible = false + setFormatText(result_cast_text, R.string.cast_format, + actors.joinToString { it.actor.name }) + } + } + } + private fun setRecommendations(rec: List?) { val isInvalid = rec.isNullOrEmpty() result_recommendations?.isGone = isInvalid @@ -540,6 +581,10 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + result_cast_items?.let { + PanelsChildGestureRegionObserver.Provider.get().register(it) + } + result_cast_items?.adapter = ActorAdaptor(mutableListOf()) fixGrid() result_recommendations?.spanCount = 3 result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE) @@ -1281,22 +1326,17 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio } } - val metadataInfoArray = ArrayList>() - if (d is AnimeLoadResponse) { - val status = when (d.showStatus) { - null -> null - ShowStatus.Ongoing -> R.string.status_ongoing - ShowStatus.Completed -> R.string.status_completed - } - if (status != null) { - metadataInfoArray.add(Pair(R.string.status, getString(status))) - } + val showStatus = when (d) { + is TvSeriesLoadResponse -> d.showStatus + is AnimeLoadResponse -> d.showStatus + else -> null } - + setShow(showStatus) setDuration(d.duration) setYear(d.year) setRating(d.rating) setRecommendations(d.recommendations) + setActors(d.actors) result_meta_site?.text = d.apiName @@ -1509,7 +1549,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) val showFillers = - settingsManager.getBoolean(ctx.getString(R.string.show_fillers_key), true) + settingsManager.getBoolean(ctx.getString(R.string.show_fillers_key), false) val tempUrl = url if (tempUrl != null) { @@ -1537,6 +1577,13 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio } } } + + PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this) + } + + override fun onPause() { + super.onPause() + PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this) } override fun onGestureRegionsUpdate(gestureRegions: List) { 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 7b1a252d..bbb8a784 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.utils +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey @@ -17,7 +18,10 @@ const val RESULT_SEASON = "result_season" const val RESULT_DUB = "result_dub" object DataStoreHelper { - data class PosDur(val position: Long, val duration: Long) + data class PosDur( + @JsonProperty("position") val position: Long, + @JsonProperty("duration") val duration: Long + ) fun PosDur.fixVisual(): PosDur { if (duration <= 0) return PosDur(0, duration) @@ -29,31 +33,31 @@ object DataStoreHelper { } data class BookmarkedData( - override val id: Int?, - val bookmarkedTime: Long, - val latestUpdatedTime: Long, - override val name: String, - override val url: String, - override val apiName: String, - override val type: TvType, - override val posterUrl: String?, - val year: Int?, + @JsonProperty("id") override val id: Int?, + @JsonProperty("bookmarkedTime") val bookmarkedTime: Long, + @JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long, + @JsonProperty("name") override val name: String, + @JsonProperty("url") override val url: String, + @JsonProperty("apiName") override val apiName: String, + @JsonProperty("type") override val type: TvType, + @JsonProperty("posterUrl") override val posterUrl: String?, + @JsonProperty("year") val year: Int?, ) : SearchResponse data class ResumeWatchingResult( - override val name: String, - override val url: String, - override val apiName: String, - override val type: TvType, - override val posterUrl: String?, + @JsonProperty("name") override val name: String, + @JsonProperty("url") override val url: String, + @JsonProperty("apiName") override val apiName: String, + @JsonProperty("type") override val type: TvType, + @JsonProperty("posterUrl") override val posterUrl: String?, - val watchPos: PosDur?, + @JsonProperty("watchPos") val watchPos: PosDur?, - override val id: Int?, - val parentId: Int?, - val episode: Int?, - val season: Int?, - val isFromDownload: Boolean, + @JsonProperty("id") override val id: Int?, + @JsonProperty("parentId") val parentId: Int?, + @JsonProperty("episode") val episode: Int?, + @JsonProperty("season") val season: Int?, + @JsonProperty("isFromDownload") val isFromDownload: Boolean, ) : SearchResponse var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION @@ -124,7 +128,7 @@ object DataStoreHelper { } fun getViewPos(id: Int?): PosDur? { - if(id == null) return null + if (id == null) return null return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) } @@ -148,7 +152,13 @@ object DataStoreHelper { } fun getResultWatchState(id: Int): WatchType { - return WatchType.fromInternalId(getKey("$currentAccount/$RESULT_WATCH_STATE", id.toString(), null)) + return WatchType.fromInternalId( + getKey( + "$currentAccount/$RESULT_WATCH_STATE", + id.toString(), + null + ) + ) } fun getResultSeason(id: Int): Int { 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 e4a0deb7..0a41aa4f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt @@ -1,37 +1,38 @@ package com.lagradost.cloudstream3.utils +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.ui.download.EasyDownloadButton object VideoDownloadHelper { data class DownloadEpisodeCached( - val name: String?, - val poster: String?, - val episode: Int, - val season: Int?, - override val id: Int, - val parentId: Int, - val rating: Int?, - val description: String?, - val cacheTime: Long, + @JsonProperty("name") val name: String?, + @JsonProperty("poster") val poster: String?, + @JsonProperty("episode") val episode: Int, + @JsonProperty("season") val season: Int?, + @JsonProperty("id") override val id: Int, + @JsonProperty("parentId") val parentId: Int, + @JsonProperty("rating") val rating: Int?, + @JsonProperty("description") val description: String?, + @JsonProperty("cacheTime") val cacheTime: Long, ) : EasyDownloadButton.IMinimumData data class DownloadHeaderCached( - val apiName: String, - val url: String, - val type: TvType, - val name: String, - val poster: String?, - val id: Int, - val cacheTime: Long, + @JsonProperty("apiName") val apiName: String, + @JsonProperty("url") val url: String, + @JsonProperty("type") val type: TvType, + @JsonProperty("name") val name: String, + @JsonProperty("poster") val poster: String?, + @JsonProperty("id") val id: Int, + @JsonProperty("cacheTime") val cacheTime: Long, ) data class ResumeWatching( - val parentId: Int, - val episodeId: Int, - val episode: Int?, - val season: Int?, - val updateTime : Long, - val isFromDownload: Boolean, + @JsonProperty("parentId") val parentId: Int, + @JsonProperty("episodeId") val episodeId: Int, + @JsonProperty("episode") val episode: Int?, + @JsonProperty("season") val season: Int?, + @JsonProperty("updateTime") val updateTime: Long, + @JsonProperty("isFromDownload") val isFromDownload: Boolean, ) } \ No newline at end of file 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 1ed09231..2da83cfa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -108,44 +108,44 @@ object VideoDownloadManager { } data class DownloadEpisodeMetadata( - val id: Int, - val mainName: String, - val sourceApiName: String?, - val poster: String?, - val name: String?, - val season: Int?, - val episode: Int? + @JsonProperty("id") val id: Int, + @JsonProperty("mainName") val mainName: String, + @JsonProperty("sourceApiName") val sourceApiName: String?, + @JsonProperty("poster") val poster: String?, + @JsonProperty("name") val name: String?, + @JsonProperty("season") val season: Int?, + @JsonProperty("episode") val episode: Int? ) data class DownloadItem( - val source: String?, - val folder: String?, - val ep: DownloadEpisodeMetadata, - val links: List, + @JsonProperty("source") val source: String?, + @JsonProperty("folder") val folder: String?, + @JsonProperty("ep") val ep: DownloadEpisodeMetadata, + @JsonProperty("links") val links: List, ) data class DownloadResumePackage( - val item: DownloadItem, - val linkIndex: Int?, + @JsonProperty("item") val item: DownloadItem, + @JsonProperty("linkIndex") val linkIndex: Int?, ) data class DownloadedFileInfo( - val totalBytes: Long, - val relativePath: String, - val displayName: String, - val extraInfo: String? = null, - val basePath: String? = null // null is for legacy downloads. See getDefaultPath() + @JsonProperty("totalBytes") val totalBytes: Long, + @JsonProperty("relativePath") val relativePath: String, + @JsonProperty("displayName") val displayName: String, + @JsonProperty("extraInfo") val extraInfo: String? = null, + @JsonProperty("basePath") val basePath: String? = null // null is for legacy downloads. See getDefaultPath() ) data class DownloadedFileInfoResult( - val fileLength: Long, - val totalBytes: Long, - val path: Uri, + @JsonProperty("fileLength") val fileLength: Long, + @JsonProperty("totalBytes") val totalBytes: Long, + @JsonProperty("path") val path: Uri, ) data class DownloadQueueResumePackage( - val index: Int, - val pkg: DownloadResumePackage, + @JsonProperty("index") val index: Int, + @JsonProperty("pkg") val pkg: DownloadResumePackage, ) private const val SUCCESS_DOWNLOAD_DONE = 1 diff --git a/app/src/main/java/com/lagradost/cloudstream3/widget/FlowLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/widget/FlowLayout.kt index 4e8097cf..c4ecf5e2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/widget/FlowLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/widget/FlowLayout.kt @@ -8,10 +8,19 @@ import com.lagradost.cloudstream3.R import kotlin.math.max class FlowLayout : ViewGroup { + var itemSpacing : Int = 0 + constructor(context: Context?) : super(context) - @JvmOverloads - constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) + //@JvmOverloads + //constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) + + @SuppressLint("CustomViewStyleable") + internal constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) { + val t = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout) + itemSpacing = t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_itemSpacing, 0); + t.recycle() + } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val realWidth = MeasureSpec.getSize(widthMeasureSpec) @@ -29,13 +38,13 @@ class FlowLayout : ViewGroup { //check if child can be placed in the current row, else go to next line if (currentChildHookPointx + childWidth > realWidth) { //new line - currentWidth = Math.max(currentWidth, currentChildHookPointx) + currentWidth = max(currentWidth, currentChildHookPointx) //reset for new line currentChildHookPointx = 0 currentChildHookPointy += childHeight } - val nextChildHookPointx = currentChildHookPointx + childWidth + val nextChildHookPointx = currentChildHookPointx + childWidth + if(childWidth == 0) 0 else itemSpacing val nextChildHookPointy = currentChildHookPointy currentHeight = max(currentHeight, currentChildHookPointy + childHeight) val lp = child.layoutParams as LayoutParams @@ -44,7 +53,7 @@ class FlowLayout : ViewGroup { currentChildHookPointx = nextChildHookPointx currentChildHookPointy = nextChildHookPointy } - currentWidth = Math.max(currentChildHookPointx, currentWidth) + currentWidth = max(currentChildHookPointx, currentWidth) setMeasuredDimension(resolveSize(currentWidth, widthMeasureSpec), resolveSize(currentHeight, heightMeasureSpec)) } @@ -83,7 +92,7 @@ class FlowLayout : ViewGroup { @SuppressLint("CustomViewStyleable") internal constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) { val t = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout) - spacing = 0 //t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_layout_space, 0); + spacing = 0//t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_itemSpacing, 0); t.recycle() } diff --git a/app/src/main/res/layout/cast_item.xml b/app/src/main/res/layout/cast_item.xml new file mode 100644 index 00000000..badc83bb --- /dev/null +++ b/app/src/main/res/layout/cast_item.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 7789cac9..246fdb95 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -506,8 +506,10 @@ \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index e7fab18e..8e54558f 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -311,6 +311,7 @@ android:layout_height="wrap_content" /> @@ -322,56 +323,28 @@ + style="@style/ResultInfoText" + tools:text="Movie" /> + style="@style/ResultInfoText" /> + style="@style/ResultInfoText" + tools:text="Rated: 8.5/10.0" /> - tools:text="121min" - android:layout_gravity="center_vertical" - android:textColor="?attr/textColor" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> + + + + + + + - - + + + + - + - - - - + + + + - - - - + + + + - - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 28f71231..62302cf1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,6 +46,7 @@ %.1f/10.0 %d %s Ep %d + Cast: %s Poster @@ -384,4 +385,7 @@ Loaded %s Load from file Downloaded file + Main + Supporting + Source diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 740896ba..8538feb5 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -229,6 +229,19 @@ @font/google_sans + + diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index ad7bafa7..6835be02 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -95,7 +95,7 @@ android:key="@string/show_fillers_key" android:icon="@drawable/ic_baseline_skip_next_24" android:title="@string/show_fillers_settings" - android:defaultValue="true" /> + android:defaultValue="false" />