forked from recloudstream/cloudstream
		
	cast + minify (json fix) + ongoing + UI change
This commit is contained in:
		
							parent
							
								
									bd14fad607
								
							
						
					
					
						commit
						91be244d61
					
				
					 24 changed files with 731 additions and 270 deletions
				
			
		|  | @ -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 { | ||||
|  |  | |||
|  | @ -189,12 +189,13 @@ object APIHolder { | |||
|         return realSet | ||||
|     } | ||||
| 
 | ||||
|     fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired : Boolean = true): List<MainAPI> { | ||||
|     fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List<MainAPI> { | ||||
|         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<SearchResponse>? | ||||
|     var actors: List<ActorData>? | ||||
| 
 | ||||
|     companion object { | ||||
|         fun LoadResponse.setActorNames(actors: List<String>?) { | ||||
|             this.actors = actors?.map { ActorData(Actor(it)) } | ||||
|         } | ||||
| 
 | ||||
|         fun LoadResponse.setActors(actors: List<Pair<Actor, String?>>?) { | ||||
|             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<SearchResponse>? = null, | ||||
|     override var actors: List<ActorData>? = 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<SearchResponse>? = null, | ||||
|     override var actors: List<ActorData>? = null, | ||||
| ) : LoadResponse | ||||
| 
 | ||||
| fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<AnimeEpisode>?) { | ||||
|  | @ -591,6 +643,7 @@ data class MovieLoadResponse( | |||
|     override var duration: Int? = null, | ||||
|     override var trailerUrl: String? = null, | ||||
|     override var recommendations: List<SearchResponse>? = null, | ||||
|     override var actors: List<ActorData>? = 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<SearchResponse>? = null, | ||||
|     override var actors: List<ActorData>? = null, | ||||
| ) : LoadResponse | ||||
| 
 | ||||
| fun MainAPI.newTvSeriesLoadResponse( | ||||
|  | @ -682,6 +718,7 @@ fun fetchUrls(text: String?): List<String> { | |||
|     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() | ||||
| } | ||||
|  |  | |||
|  | @ -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<GogoSource>?, | ||||
|         val sourceBk: List<GogoSource>?, | ||||
|         @JsonProperty("source") val source: List<GogoSource>?, | ||||
|         @JsonProperty("sourceBk") val sourceBk: List<GogoSource>?, | ||||
|         //val track: List<Any?>, | ||||
|         //val advertising: List<Any?>, | ||||
|         //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) { | ||||
|  |  | |||
|  | @ -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 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<CastMember?>?.toActors(): List<Pair<Actor, String?>>? { | ||||
|         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() | ||||
|             } | ||||
|     } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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<SearchResponse> { | ||||
|         return search(query) | ||||
|  | @ -106,7 +109,16 @@ class MeloMovieProvider : MainAPI() { | |||
|     ): Boolean { | ||||
|         val links = parseJson<List<MeloMovieLink>>(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<TvSeriesEpisode>() | ||||
|             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)) | ||||
|                 } | ||||
|  |  | |||
|  | @ -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<HomePageList>() | ||||
|         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<SearchResponse> { | ||||
|         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<TvSeriesEpisode>() | ||||
| 
 | ||||
|             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) | ||||
|                 } | ||||
|             } | ||||
|  |  | |||
|  | @ -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<HomePageList>() | ||||
|         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<SearchResponse> { | ||||
|         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) | ||||
|                 } | ||||
|             } | ||||
|  |  | |||
|  | @ -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<String>? = null | ||||
|         var cast: List<String>? = 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 | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -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 | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  |  | |||
|  | @ -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<ActorData>, | ||||
| ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { | ||||
| 
 | ||||
|     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<ActorData>) { | ||||
|         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<ActorData>, | ||||
|     private val newList: List<ActorData> | ||||
| ) : | ||||
|     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] | ||||
| } | ||||
|  | @ -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<ActorData>?) { | ||||
|         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<SearchResponse>?) { | ||||
|         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<Pair<Int, String>>() | ||||
|                         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<Rect>) { | ||||
|  |  | |||
|  | @ -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<Int>("$currentAccount/$RESULT_WATCH_STATE", id.toString(), null)) | ||||
|         return WatchType.fromInternalId( | ||||
|             getKey<Int>( | ||||
|                 "$currentAccount/$RESULT_WATCH_STATE", | ||||
|                 id.toString(), | ||||
|                 null | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     fun getResultSeason(id: Int): Int { | ||||
|  |  | |||
|  | @ -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, | ||||
|     ) | ||||
| } | ||||
|  | @ -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<ExtractorLink>, | ||||
|         @JsonProperty("source") val source: String?, | ||||
|         @JsonProperty("folder") val folder: String?, | ||||
|         @JsonProperty("ep") val ep: DownloadEpisodeMetadata, | ||||
|         @JsonProperty("links") val links: List<ExtractorLink>, | ||||
|     ) | ||||
| 
 | ||||
|     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 | ||||
|  |  | |||
|  | @ -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() | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										107
									
								
								app/src/main/res/layout/cast_item.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								app/src/main/res/layout/cast_item.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,107 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.cardview.widget.CardView 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:nextFocusLeft="@id/episode_poster" | ||||
|         android:nextFocusRight="@id/result_episode_download" | ||||
|         android:id="@+id/episode_holder" | ||||
| 
 | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         app:cardCornerRadius="@dimen/rounded_image_radius" | ||||
|         app:cardBackgroundColor="?attr/boxItemBackground" | ||||
| 
 | ||||
|         android:foreground="@drawable/outline_drawable" | ||||
|         android:layout_marginEnd="10dp"> | ||||
| 
 | ||||
| 
 | ||||
|     <LinearLayout | ||||
|             android:layout_width="100dp" | ||||
|             android:orientation="vertical" | ||||
|             android:layout_height="wrap_content"> | ||||
|         <!--app:cardCornerRadius="@dimen/roundedImageRadius"--> | ||||
|         <FrameLayout | ||||
|                 android:layout_gravity="center_horizontal" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content"> | ||||
| 
 | ||||
|             <androidx.cardview.widget.CardView | ||||
|                     app:cardCornerRadius="70dp" | ||||
|                     android:layout_width="70dp" | ||||
|                     android:layout_height="70dp" | ||||
|                     android:foreground="@drawable/outline_drawable"> | ||||
| 
 | ||||
|                 <ImageView | ||||
|                         android:nextFocusLeft="@id/result_episode_download" | ||||
|                         android:nextFocusRight="@id/episode_holder" | ||||
| 
 | ||||
|                         android:id="@+id/actor_image" | ||||
|                         tools:src="@drawable/example_poster" | ||||
| 
 | ||||
|                         android:scaleType="centerCrop" | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="match_parent" | ||||
|                         android:contentDescription="@string/episode_poster_img_des" /> | ||||
| 
 | ||||
|             </androidx.cardview.widget.CardView> | ||||
| 
 | ||||
|             <androidx.cardview.widget.CardView | ||||
|                     android:id="@+id/voice_actor_image_holder" | ||||
|                     android:layout_gravity="end|bottom" | ||||
|                     app:cardCornerRadius="40dp" | ||||
|                     android:layout_width="40dp" | ||||
|                     android:layout_height="40dp" | ||||
|                     android:foreground="@drawable/outline_drawable"> | ||||
| 
 | ||||
|                 <ImageView | ||||
|                         android:nextFocusLeft="@id/result_episode_download" | ||||
|                         android:nextFocusRight="@id/episode_holder" | ||||
| 
 | ||||
|                         android:id="@+id/voice_actor_image" | ||||
|                         tools:src="@drawable/example_poster" | ||||
| 
 | ||||
|                         android:scaleType="centerCrop" | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="match_parent" | ||||
|                         android:contentDescription="@string/episode_poster_img_des" /> | ||||
|             </androidx.cardview.widget.CardView> | ||||
|         </FrameLayout> | ||||
| 
 | ||||
|         <LinearLayout | ||||
|                 android:padding="10dp" | ||||
|                 android:orientation="vertical" | ||||
|                 android:layout_gravity="center" | ||||
|                 android:gravity="center_horizontal" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content"> | ||||
| 
 | ||||
|             <TextView | ||||
|                     android:gravity="center_horizontal" | ||||
|                     android:id="@+id/actor_name" | ||||
|                     tools:text="Ackerman, Mikasa" | ||||
|                     android:textStyle="bold" | ||||
|                     android:textColor="?attr/textColor" | ||||
|                     android:layout_width="wrap_content" | ||||
|                     android:layout_height="wrap_content" /> | ||||
| 
 | ||||
|             <TextView | ||||
|                     android:gravity="center_horizontal" | ||||
|                     android:id="@+id/voice_actor_name" | ||||
|                     tools:text="voiceactor" | ||||
|                     android:textColor="?attr/grayTextColor" | ||||
|                     android:layout_width="wrap_content" | ||||
|                     android:layout_height="wrap_content" /> | ||||
| 
 | ||||
|             <TextView | ||||
|                     android:gravity="center_horizontal" | ||||
|                     android:id="@+id/actor_extra" | ||||
|                     tools:text="Main" | ||||
|                     android:textColor="?attr/grayTextColor" | ||||
|                     android:layout_width="wrap_content" | ||||
|                     android:layout_height="wrap_content" /> | ||||
| 
 | ||||
|         </LinearLayout> | ||||
| 
 | ||||
|     </LinearLayout> | ||||
| </androidx.cardview.widget.CardView> | ||||
|  | @ -506,8 +506,10 @@ | |||
|     <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton | ||||
|             android:visibility="gone" | ||||
|             tools:visibility="visible" | ||||
|             android:text="@string/home_source" | ||||
|             android:id="@+id/home_api_fab" | ||||
|             app:icon="@drawable/ic_baseline_filter_list_24" | ||||
|             style="@style/ExtendedFloatingActionButton" | ||||
|             android:textColor="?attr/textColor" | ||||
|             tools:ignore="ContentDescription" /> | ||||
| </FrameLayout> | ||||
|  | @ -311,6 +311,7 @@ | |||
|                                     android:layout_height="wrap_content" /> | ||||
| 
 | ||||
|                             <com.lagradost.cloudstream3.widget.FlowLayout | ||||
|                                     app:itemSpacing="10dp" | ||||
|                                     android:layout_width="match_parent" | ||||
|                                     android:layout_height="wrap_content"> | ||||
| 
 | ||||
|  | @ -322,56 +323,28 @@ | |||
| 
 | ||||
|                                 <TextView | ||||
|                                         android:id="@+id/result_meta_type" | ||||
|                                         android:paddingStart="5dp" | ||||
|                                         android:paddingEnd="5dp" | ||||
|                                         android:minHeight="24dp" | ||||
|                                         android:gravity="center" | ||||
| 
 | ||||
|                                         tools:text="Movie" | ||||
|                                         android:layout_gravity="center_vertical" | ||||
|                                         android:textColor="?attr/textColor" | ||||
|                                         android:layout_width="wrap_content" | ||||
|                                         android:layout_height="wrap_content" /> | ||||
|                                         style="@style/ResultInfoText" | ||||
|                                         tools:text="Movie" /> | ||||
| 
 | ||||
|                                 <TextView | ||||
|                                         android:paddingStart="5dp" | ||||
|                                         android:paddingEnd="5dp" | ||||
|                                         android:minHeight="24dp" | ||||
|                                         android:gravity="center" | ||||
| 
 | ||||
|                                         tools:text="2022" | ||||
|                                         android:id="@+id/result_meta_year" | ||||
|                                         android:layout_marginStart="10dp" | ||||
|                                         tools:text="2021" | ||||
|                                         android:layout_gravity="center_vertical" | ||||
|                                         android:textColor="?attr/white" | ||||
|                                         android:layout_width="wrap_content" | ||||
|                                         android:layout_height="wrap_content" /> | ||||
|                                         style="@style/ResultInfoText" /> | ||||
| 
 | ||||
|                                 <TextView | ||||
|                                         android:id="@+id/result_meta_rating" | ||||
|                                         android:paddingStart="5dp" | ||||
|                                         android:paddingEnd="5dp" | ||||
|                                         android:minHeight="24dp" | ||||
|                                         android:gravity="center" | ||||
| 
 | ||||
|                                         tools:text="Rated: 8.5/10.0" | ||||
|                                         android:layout_gravity="center_vertical" | ||||
|                                         android:textColor="?attr/textColor" | ||||
|                                         android:layout_width="wrap_content" | ||||
|                                         android:layout_height="wrap_content" /> | ||||
|                                         style="@style/ResultInfoText" | ||||
|                                         tools:text="Rated: 8.5/10.0" /> | ||||
| 
 | ||||
|                                 <TextView | ||||
|                                         android:paddingStart="5dp" | ||||
|                                         android:paddingEnd="5dp" | ||||
|                                         android:minHeight="24dp" | ||||
|                                         android:gravity="center" | ||||
|                                         android:id="@+id/result_meta_duration" | ||||
|                                         android:id="@+id/result_meta_status" | ||||
|                                         style="@style/ResultInfoText" | ||||
|                                         tools:text="Ongoing" /> | ||||
| 
 | ||||
|                                         tools:text="121min" | ||||
|                                         android:layout_gravity="center_vertical" | ||||
|                                         android:textColor="?attr/textColor" | ||||
|                                         android:layout_width="wrap_content" | ||||
|                                         android:layout_height="wrap_content" /> | ||||
|                                 <TextView | ||||
|                                         style="@style/ResultInfoText" | ||||
|                                         android:id="@+id/result_meta_duration" | ||||
|                                         tools:text="121min" /> | ||||
|                             </com.lagradost.cloudstream3.widget.FlowLayout> | ||||
| 
 | ||||
|                             <FrameLayout | ||||
|  | @ -428,6 +401,28 @@ | |||
|                         <requestFocus /> | ||||
|                     </com.google.android.material.button.MaterialButton> | ||||
| 
 | ||||
|                     <TextView | ||||
|                             android:maxLines="2" | ||||
|                             android:ellipsize="end" | ||||
|                             android:layout_marginBottom="5dp" | ||||
|                             android:textColor="?attr/grayTextColor" | ||||
|                             android:id="@+id/result_cast_text" | ||||
|                             android:textSize="15sp" | ||||
|                             tools:text="Cast: Joe Ligma" | ||||
|                             android:layout_width="wrap_content" | ||||
|                             android:layout_height="wrap_content" /> | ||||
| 
 | ||||
|                     <androidx.recyclerview.widget.RecyclerView | ||||
|                             tools:itemCount="2" | ||||
|                             android:fadingEdge="horizontal" | ||||
|                             android:requiresFadingEdge="horizontal" | ||||
|                             android:orientation="horizontal" | ||||
|                             app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" | ||||
|                             android:id="@+id/result_cast_items" | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             tools:listitem="@layout/cast_item" /> | ||||
| 
 | ||||
|                     <TextView | ||||
|                             android:textColor="?attr/grayTextColor" | ||||
|                             android:id="@+id/result_vpn" | ||||
|  | @ -564,6 +559,7 @@ | |||
|                                 android:text="@string/resume" | ||||
|                                 app:icon="@drawable/ic_baseline_play_arrow_24" | ||||
|                                 android:layout_width="match_parent" /> | ||||
| 
 | ||||
|                         <com.google.android.material.button.MaterialButton | ||||
|                                 android:layout_marginBottom="10dp" | ||||
|                                 android:nextFocusUp="@id/result_bookmark_button" | ||||
|  | @ -607,6 +603,7 @@ | |||
|                                     android:layout_weight="1" | ||||
|                                     android:visibility="visible" | ||||
|                                     tools:visibility="visible" /> | ||||
| 
 | ||||
|                             <TextView | ||||
|                                     android:id="@+id/result_resume_series_progress_text" | ||||
|                                     android:layout_gravity="center" | ||||
|  |  | |||
|  | @ -1,10 +1,12 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <declare-styleable name="FlowLayout_Layout"/> | ||||
|     <declare-styleable name="FlowLayout_Layout_layout_space"/> | ||||
|     <declare-styleable name="FlowLayout_Layout"> | ||||
|         <attr format="dimension" name="itemSpacing" /> | ||||
|     </declare-styleable> | ||||
|     <declare-styleable name="FlowLayout_Layout_layout_space" /> | ||||
| 
 | ||||
|     <declare-styleable name="CustomCast"> | ||||
|         <attr name="customCastBackgroundColor" format="color"/> | ||||
|         <attr name="customCastBackgroundColor" format="color" /> | ||||
|     </declare-styleable> | ||||
| 
 | ||||
|     <style name="customCastDefColor"> | ||||
|  | @ -12,19 +14,19 @@ | |||
|     </style> | ||||
| 
 | ||||
|     <declare-styleable name="MainColors"> | ||||
|         <attr name="colorPrimary" format="color"/> | ||||
|         <attr name="colorSearch" format="color"/> | ||||
|         <attr name="colorOngoing" format="color"/> | ||||
|         <attr name="colorPrimaryDark" format="color"/> | ||||
|         <attr name="colorPrimary" format="color" /> | ||||
|         <attr name="colorSearch" format="color" /> | ||||
|         <attr name="colorOngoing" format="color" /> | ||||
|         <attr name="colorPrimaryDark" format="color" /> | ||||
| 
 | ||||
|         <attr name="primaryGrayBackground" format="color"/> | ||||
|         <attr name="primaryBlackBackground" format="color"/> | ||||
|         <attr name="iconGrayBackground" format="color"/> | ||||
|         <attr name="boxItemBackground" format="color"/> | ||||
|         <attr name="primaryGrayBackground" format="color" /> | ||||
|         <attr name="primaryBlackBackground" format="color" /> | ||||
|         <attr name="iconGrayBackground" format="color" /> | ||||
|         <attr name="boxItemBackground" format="color" /> | ||||
| 
 | ||||
|         <attr name="textColor" format="color"/> | ||||
|         <attr name="grayTextColor" format="color"/> | ||||
|         <attr name="iconColor" format="color"/> | ||||
|         <attr name="white" format="color"/> | ||||
|         <attr name="textColor" format="color" /> | ||||
|         <attr name="grayTextColor" format="color" /> | ||||
|         <attr name="iconColor" format="color" /> | ||||
|         <attr name="white" format="color" /> | ||||
|     </declare-styleable> | ||||
| </resources> | ||||
|  | @ -46,6 +46,7 @@ | |||
|     <string name="rating_format" translatable="false" formatted="true">%.1f/10.0</string> | ||||
|     <string name="year_format" translatable="false" formatted="true">%d</string> | ||||
|     <string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string> | ||||
|     <string name="cast_format" formatted="true">Cast: %s</string> | ||||
| 
 | ||||
|     <!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS --> | ||||
|     <string name="result_poster_img_des">Poster</string> | ||||
|  | @ -384,4 +385,7 @@ | |||
|     <string name="player_loaded_subtitles" formatted="true">Loaded %s</string> | ||||
|     <string name="player_load_subtitles">Load from file</string> | ||||
|     <string name="downloaded_file">Downloaded file</string> | ||||
|     <string name="actor_main">Main</string> | ||||
|     <string name="actor_supporting">Supporting</string> | ||||
|     <string name="home_source">Source</string> | ||||
| </resources> | ||||
|  |  | |||
|  | @ -229,6 +229,19 @@ | |||
|         <item name="android:fontFamily">@font/google_sans</item> | ||||
|     </style> | ||||
| 
 | ||||
|     <style name="ResultInfoText"> | ||||
|         <item name="android:layout_gravity">center_vertical</item> | ||||
|         <item name="textColor">?attr/white</item> | ||||
| 
 | ||||
|         <item name="android:gravity">center</item> | ||||
|         <item name="android:layout_width">wrap_content</item> | ||||
|         <item name="android:layout_height">wrap_content</item> | ||||
|         <item name="android:minHeight">24dp</item> | ||||
|         <item name="android:minWidth">0dp</item> | ||||
|      <!--   <item name="android:paddingStart">5dp</item> | ||||
|         <item name="android:paddingEnd">5dp</item>--> | ||||
|     </style> | ||||
| 
 | ||||
|     <style name="AppMaterialButtonStyle" parent="Widget.MaterialComponents.Button"> | ||||
|         <item name="android:fontFamily">@font/google_sans</item> | ||||
|     </style> | ||||
|  |  | |||
|  | @ -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" /> | ||||
|         <Preference | ||||
|                 android:key="@string/dns_key" | ||||
|                 android:title="@string/dns_pref" | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue