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 |         targetSdkVersion 30 | ||||||
| 
 | 
 | ||||||
|         versionCode 42 |         versionCode 42 | ||||||
|         versionName "2.6.9" |         versionName "2.6.10" | ||||||
| 
 | 
 | ||||||
|         resValue "string", "app_version", |         resValue "string", "app_version", | ||||||
|                 "${defaultConfig.versionName}${versionNameSuffix ?: ""}" |                 "${defaultConfig.versionName}${versionNameSuffix ?: ""}" | ||||||
|  | @ -52,8 +52,9 @@ android { | ||||||
| 
 | 
 | ||||||
|     buildTypes { |     buildTypes { | ||||||
|         release { |         release { | ||||||
|             minifyEnabled false |             debuggable false | ||||||
|             debuggable true |             minifyEnabled true | ||||||
|  |             shrinkResources true | ||||||
|             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | ||||||
|         } |         } | ||||||
|         prerelease { |         prerelease { | ||||||
|  | @ -61,12 +62,15 @@ android { | ||||||
|             buildConfigField("boolean", "BETA", "true") |             buildConfigField("boolean", "BETA", "true") | ||||||
|             signingConfig signingConfigs.prerelease |             signingConfig signingConfigs.prerelease | ||||||
|             versionNameSuffix '-PRE' |             versionNameSuffix '-PRE' | ||||||
|             minifyEnabled false |             minifyEnabled true | ||||||
|             debuggable true |             debuggable false | ||||||
|  |             shrinkResources true | ||||||
|             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | ||||||
|         } |         } | ||||||
|         debug { |         debug { | ||||||
|  |             debuggable true | ||||||
|             applicationIdSuffix ".debug" |             applicationIdSuffix ".debug" | ||||||
|  |             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     compileOptions { |     compileOptions { | ||||||
|  |  | ||||||
|  | @ -189,12 +189,13 @@ object APIHolder { | ||||||
|         return realSet |         return realSet | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired : Boolean = true): List<MainAPI> { |     fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List<MainAPI> { | ||||||
|         val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) |         val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) | ||||||
|         val currentPrefMedia = |         val currentPrefMedia = | ||||||
|             settingsManager.getInt(this.getString(R.string.prefer_media_type_key), 0) |             settingsManager.getInt(this.getString(R.string.prefer_media_type_key), 0) | ||||||
|         val langs = this.getApiProviderLangSettings() |         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) { |         return if (currentPrefMedia < 1) { | ||||||
|             allApis |             allApis | ||||||
|         } else { |         } else { | ||||||
|  | @ -426,6 +427,23 @@ interface SearchResponse { | ||||||
|     val id: Int? |     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( | data class AnimeSearchResponse( | ||||||
|     override val name: String, |     override val name: String, | ||||||
|     override val url: String, |     override val url: String, | ||||||
|  | @ -488,6 +506,38 @@ interface LoadResponse { | ||||||
|     var duration: Int? // in minutes |     var duration: Int? // in minutes | ||||||
|     val trailerUrl: String? |     val trailerUrl: String? | ||||||
|     val recommendations: List<SearchResponse>? |     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 { | fun LoadResponse?.isEpisodeBased(): Boolean { | ||||||
|  | @ -530,6 +580,7 @@ data class TorrentLoadResponse( | ||||||
|     override var duration: Int? = null, |     override var duration: Int? = null, | ||||||
|     override var trailerUrl: String? = null, |     override var trailerUrl: String? = null, | ||||||
|     override var recommendations: List<SearchResponse>? = null, |     override var recommendations: List<SearchResponse>? = null, | ||||||
|  |     override var actors: List<ActorData>? = null, | ||||||
| ) : LoadResponse | ) : LoadResponse | ||||||
| 
 | 
 | ||||||
| data class AnimeLoadResponse( | data class AnimeLoadResponse( | ||||||
|  | @ -556,6 +607,7 @@ data class AnimeLoadResponse( | ||||||
|     override var duration: Int? = null, |     override var duration: Int? = null, | ||||||
|     override var trailerUrl: String? = null, |     override var trailerUrl: String? = null, | ||||||
|     override var recommendations: List<SearchResponse>? = null, |     override var recommendations: List<SearchResponse>? = null, | ||||||
|  |     override var actors: List<ActorData>? = null, | ||||||
| ) : LoadResponse | ) : LoadResponse | ||||||
| 
 | 
 | ||||||
| fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<AnimeEpisode>?) { | fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<AnimeEpisode>?) { | ||||||
|  | @ -591,6 +643,7 @@ data class MovieLoadResponse( | ||||||
|     override var duration: Int? = null, |     override var duration: Int? = null, | ||||||
|     override var trailerUrl: String? = null, |     override var trailerUrl: String? = null, | ||||||
|     override var recommendations: List<SearchResponse>? = null, |     override var recommendations: List<SearchResponse>? = null, | ||||||
|  |     override var actors: List<ActorData>? = null, | ||||||
| ) : LoadResponse | ) : LoadResponse | ||||||
| 
 | 
 | ||||||
| fun MainAPI.newMovieLoadResponse( | fun MainAPI.newMovieLoadResponse( | ||||||
|  | @ -611,24 +664,6 @@ fun MainAPI.newMovieLoadResponse( | ||||||
|     return builder |     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( | data class TvSeriesEpisode( | ||||||
|     val name: String? = null, |     val name: String? = null, | ||||||
|     val season: Int? = null, |     val season: Int? = null, | ||||||
|  | @ -658,6 +693,7 @@ data class TvSeriesLoadResponse( | ||||||
|     override var duration: Int? = null, |     override var duration: Int? = null, | ||||||
|     override var trailerUrl: String? = null, |     override var trailerUrl: String? = null, | ||||||
|     override var recommendations: List<SearchResponse>? = null, |     override var recommendations: List<SearchResponse>? = null, | ||||||
|  |     override var actors: List<ActorData>? = null, | ||||||
| ) : LoadResponse | ) : LoadResponse | ||||||
| 
 | 
 | ||||||
| fun MainAPI.newTvSeriesLoadResponse( | fun MainAPI.newTvSeriesLoadResponse( | ||||||
|  | @ -682,6 +718,7 @@ fun fetchUrls(text: String?): List<String> { | ||||||
|     if (text.isNullOrEmpty()) { |     if (text.isNullOrEmpty()) { | ||||||
|         return listOf() |         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() |     return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package com.lagradost.cloudstream3.animeproviders | package com.lagradost.cloudstream3.animeproviders | ||||||
| 
 | 
 | ||||||
|  | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
| import com.lagradost.cloudstream3.* | import com.lagradost.cloudstream3.* | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils | import com.lagradost.cloudstream3.utils.AppUtils | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
|  | @ -218,18 +219,18 @@ class GogoanimeProvider : MainAPI() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     data class GogoSources( |     data class GogoSources( | ||||||
|         val source: List<GogoSource>?, |         @JsonProperty("source") val source: List<GogoSource>?, | ||||||
|         val sourceBk: List<GogoSource>?, |         @JsonProperty("sourceBk") val sourceBk: List<GogoSource>?, | ||||||
|         //val track: List<Any?>, |         //val track: List<Any?>, | ||||||
|         //val advertising: List<Any?>, |         //val advertising: List<Any?>, | ||||||
|         //val linkiframe: String |         //val linkiframe: String | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     data class GogoSource( |     data class GogoSource( | ||||||
|         val file: String, |         @JsonProperty("file") val file: String, | ||||||
|         val label: String?, |         @JsonProperty("label") val label: String?, | ||||||
|         val type: String?, |         @JsonProperty("type") val type: String?, | ||||||
|         val default: String? = null |         @JsonProperty("default") val default: String? = null | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     private suspend fun extractVideos(uri: String, callback: (ExtractorLink) -> Unit) { |     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 { |     override suspend fun load(url: String): LoadResponse { | ||||||
|         val html = app.get(url).text |         val html = app.get(url).text | ||||||
|         val document = Jsoup.parse(html) |         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 = |         val recommendations = | ||||||
|             document.select("#main-content > section > .tab-content > div > .film_list-wrap > .flw-item") |             document.select("#main-content > section > .tab-content > div > .film_list-wrap > .flw-item") | ||||||
|                 .mapNotNull { head -> |                 .mapNotNull { head -> | ||||||
|  | @ -254,6 +278,7 @@ class ZoroProvider : MainAPI() { | ||||||
|             plot = description |             plot = description | ||||||
|             this.tags = tags |             this.tags = tags | ||||||
|             this.recommendations = recommendations |             this.recommendations = recommendations | ||||||
|  |             this.actors = actors | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package com.lagradost.cloudstream3.metaproviders | ||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.annotation.JsonProperty | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
| import com.lagradost.cloudstream3.* | import com.lagradost.cloudstream3.* | ||||||
|  | import com.lagradost.cloudstream3.LoadResponse.Companion.setActors | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.toJson | import com.lagradost.cloudstream3.utils.AppUtils.toJson | ||||||
| import com.uwetrottmann.tmdb2.Tmdb | import com.uwetrottmann.tmdb2.Tmdb | ||||||
| import com.uwetrottmann.tmdb2.entities.* | 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 { |     private fun TvShow.toLoadResponse(): TvSeriesLoadResponse { | ||||||
|         val episodes = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 } |         val episodes = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 } | ||||||
|             ?.mapNotNull { season -> |             ?.mapNotNull { season -> | ||||||
|  | @ -112,57 +122,56 @@ open class TmdbProvider : MainAPI() { | ||||||
|                 } |                 } | ||||||
|             }?.flatten() ?: listOf() |             }?.flatten() ?: listOf() | ||||||
| 
 | 
 | ||||||
|         return TvSeriesLoadResponse( |         return newTvSeriesLoadResponse( | ||||||
|             this.name ?: this.original_name, |             this.name ?: this.original_name, | ||||||
|             getUrl(id, true), |             getUrl(id, true), | ||||||
|             this@TmdbProvider.apiName, |  | ||||||
|             TvType.TvSeries, |             TvType.TvSeries, | ||||||
|             episodes, |             episodes | ||||||
|             getImageUrl(this.poster_path), |         ) { | ||||||
|             this.first_air_date?.let { |             posterUrl = getImageUrl(poster_path) | ||||||
|  |             year = first_air_date?.let { | ||||||
|                 Calendar.getInstance().apply { |                 Calendar.getInstance().apply { | ||||||
|                     time = it |                     time = it | ||||||
|                 }.get(Calendar.YEAR) |                 }.get(Calendar.YEAR) | ||||||
|             }, |             } | ||||||
|             this.overview, |             plot = overview | ||||||
|             null, // this.status |             imdbId = external_ids?.imdb_id | ||||||
|             this.external_ids?.imdb_id, |             tags = genres?.mapNotNull { it.name } | ||||||
|             this.rating, |             duration = episode_run_time?.average()?.toInt() | ||||||
|             this.genres?.mapNotNull { it.name }, |             rating = this@toLoadResponse.rating | ||||||
|             this.episode_run_time?.average()?.toInt(), | 
 | ||||||
|             null, |             recommendations = (this@toLoadResponse.recommendations | ||||||
|             (this.recommendations ?: this.similar)?.results?.map { it.toSearchResponse() } |                 ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } | ||||||
|         ) |             setActors(credits?.cast?.toList().toActors()) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun Movie.toLoadResponse(): MovieLoadResponse { |     private fun Movie.toLoadResponse(): MovieLoadResponse { | ||||||
|         println("TRAILRES::::::: ${this.similar} :::: ${this.recommendations} ") |         return newMovieLoadResponse( | ||||||
|         return MovieLoadResponse( |             this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink( | ||||||
|             this.title ?: this.original_title, |  | ||||||
|             getUrl(id, false), |  | ||||||
|             this@TmdbProvider.apiName, |  | ||||||
|             TvType.Movie, |  | ||||||
|             TmdbLink( |  | ||||||
|                 this.imdb_id, |                 this.imdb_id, | ||||||
|                 this.id, |                 this.id, | ||||||
|                 null, |                 null, | ||||||
|                 null, |                 null, | ||||||
|                 this.title ?: this.original_title, |                 this.title ?: this.original_title, | ||||||
|             ).toJson(), |             ).toJson() | ||||||
|             getImageUrl(this.poster_path), |         ) { | ||||||
|             this.release_date?.let { |             posterUrl = getImageUrl(poster_path) | ||||||
|  |             year = release_date?.let { | ||||||
|                 Calendar.getInstance().apply { |                 Calendar.getInstance().apply { | ||||||
|                     time = it |                     time = it | ||||||
|                 }.get(Calendar.YEAR) |                 }.get(Calendar.YEAR) | ||||||
|             }, |             } | ||||||
|             this.overview, |             plot = overview | ||||||
|             null,//this.status |             imdbId = external_ids?.imdb_id | ||||||
|             this.rating, |             tags = genres?.mapNotNull { it.name } | ||||||
|             this.genres?.mapNotNull { it.name }, |             duration = runtime | ||||||
|             this.runtime, |             rating = this@toLoadResponse.rating | ||||||
|             null, | 
 | ||||||
|             (this.recommendations ?: this.similar)?.results?.map { it.toSearchResponse() } |             recommendations = (this@toLoadResponse.recommendations | ||||||
|         ) |                 ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } | ||||||
|  |             setActors(credits?.cast?.toList().toActors()) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override suspend fun getMainPage(): HomePageResponse { |     override suspend fun getMainPage(): HomePageResponse { | ||||||
|  | @ -248,23 +257,38 @@ open class TmdbProvider : MainAPI() { | ||||||
|             return if (isTvSeries) { |             return if (isTvSeries) { | ||||||
|                 val body = tmdb.tvService().tv(id, "en-US").awaitResponse().body() |                 val body = tmdb.tvService().tv(id, "en-US").awaitResponse().body() | ||||||
|                 val response = body?.toLoadResponse() |                 val response = body?.toLoadResponse() | ||||||
|                 if (response != null && response.recommendations.isNullOrEmpty()) { |                 if (response != null) { | ||||||
|                     tmdb.tvService().recommendations(id, 1,"en-US").awaitResponse().body()?.let { |                     if (response.recommendations.isNullOrEmpty()) | ||||||
|  |                         tmdb.tvService().recommendations(id, 1, "en-US").awaitResponse().body() | ||||||
|  |                             ?.let { | ||||||
|                                 it.results?.map { res -> res.toSearchResponse() } |                                 it.results?.map { res -> res.toSearchResponse() } | ||||||
|                             }?.let { list -> |                             }?.let { list -> | ||||||
|                             response.recommendations = list |                             response.recommendations = list | ||||||
|                         } |                         } | ||||||
|  | 
 | ||||||
|  |                     if (response.actors.isNullOrEmpty()) | ||||||
|  |                         tmdb.tvService().credits(id, "en-US").awaitResponse().body()?.let { | ||||||
|  |                             response.setActors(it.cast?.toActors()) | ||||||
|                         } |                         } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|                 response |                 response | ||||||
|             } else { |             } else { | ||||||
|                 val body = tmdb.moviesService().summary(id, "en-US").awaitResponse().body() |                 val body = tmdb.moviesService().summary(id, "en-US").awaitResponse().body() | ||||||
|                 val response = body?.toLoadResponse() |                 val response = body?.toLoadResponse() | ||||||
|                 if (response != null && response.recommendations.isNullOrEmpty()) { |                 if (response != null) { | ||||||
|                    tmdb.moviesService().recommendations(id, 1,"en-US").awaitResponse().body()?.let { |                     if (response.recommendations.isNullOrEmpty()) | ||||||
|  |                         tmdb.moviesService().recommendations(id, 1, "en-US").awaitResponse().body() | ||||||
|  |                             ?.let { | ||||||
|                                 it.results?.map { res -> res.toSearchResponse() } |                                 it.results?.map { res -> res.toSearchResponse() } | ||||||
|                             }?.let { list -> |                             }?.let { list -> | ||||||
|                                 response.recommendations = list |                                 response.recommendations = list | ||||||
|                             } |                             } | ||||||
|  | 
 | ||||||
|  |                     if (response.actors.isNullOrEmpty()) | ||||||
|  |                         tmdb.moviesService().credits(id).awaitResponse().body()?.let { | ||||||
|  |                             response.setActors(it.cast?.toActors()) | ||||||
|  |                         } | ||||||
|                 } |                 } | ||||||
|                 response |                 response | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package com.lagradost.cloudstream3.movieproviders | ||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.module.kotlin.readValue | import com.fasterxml.jackson.module.kotlin.readValue | ||||||
| import com.lagradost.cloudstream3.* | import com.lagradost.cloudstream3.* | ||||||
|  | import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.toJson | import com.lagradost.cloudstream3.utils.AppUtils.toJson | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import com.lagradost.cloudstream3.utils.Qualities | import com.lagradost.cloudstream3.utils.Qualities | ||||||
|  |  | ||||||
|  | @ -26,7 +26,10 @@ class MeloMovieProvider : MainAPI() { | ||||||
|         //"mppa" for tags |         //"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> { |     override suspend fun quickSearch(query: String): List<SearchResponse> { | ||||||
|         return search(query) |         return search(query) | ||||||
|  | @ -106,7 +109,16 @@ class MeloMovieProvider : MainAPI() { | ||||||
|     ): Boolean { |     ): Boolean { | ||||||
|         val links = parseJson<List<MeloMovieLink>>(data) |         val links = parseJson<List<MeloMovieLink>>(data) | ||||||
|         for (link in links) { |         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 |         return true | ||||||
|     } |     } | ||||||
|  | @ -125,11 +137,13 @@ class MeloMovieProvider : MainAPI() { | ||||||
|         val type = findUsingRegex("var posttype = ([0-9]*)")?.toInt() ?: return null |         val type = findUsingRegex("var posttype = ([0-9]*)")?.toInt() ?: return null | ||||||
|         val titleInfo = document.selectFirst("div.movie_detail_title > div > div > h1") |         val titleInfo = document.selectFirst("div.movie_detail_title > div > div > h1") | ||||||
|         val title = titleInfo.ownText() |         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() |         val plot = document.selectFirst("div.col-lg-12 > p").text() | ||||||
| 
 | 
 | ||||||
|         if (type == 1) { // MOVIE |         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( |             return MovieLoadResponse( | ||||||
|                 title, |                 title, | ||||||
|                 url, |                 url, | ||||||
|  | @ -143,15 +157,19 @@ class MeloMovieProvider : MainAPI() { | ||||||
|             ) |             ) | ||||||
|         } else if (type == 2) { |         } else if (type == 2) { | ||||||
|             val episodes = ArrayList<TvSeriesEpisode>() |             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) { |             for (s in seasons) { | ||||||
|                 val season = |                 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") |                 val localEpisodes = s.select("> div.collapse > div > div > div.accordion__card") | ||||||
|                 for (e in localEpisodes) { |                 for (e in localEpisodes) { | ||||||
|                     val episode = |                     val episode = | ||||||
|                         e.selectFirst("> div.card-header > button > span").text().replace("Episode: ", "").toIntOrNull() |                         e.selectFirst("> div.card-header > button > span").text() | ||||||
|                     val links = e.selectFirst("> div.collapse > div > table.accordion__list") ?: continue |                             .replace("Episode: ", "").toIntOrNull() | ||||||
|  |                     val links = | ||||||
|  |                         e.selectFirst("> div.collapse > div > table.accordion__list") ?: continue | ||||||
|                     val data = serializeData(links) |                     val data = serializeData(links) | ||||||
|                     episodes.add(TvSeriesEpisode(null, season, episode, data)) |                     episodes.add(TvSeriesEpisode(null, season, episode, data)) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -1,11 +1,12 @@ | ||||||
| package com.lagradost.cloudstream3.movieproviders | package com.lagradost.cloudstream3.movieproviders | ||||||
| 
 | 
 | ||||||
| import com.lagradost.cloudstream3.* | import com.lagradost.cloudstream3.* | ||||||
|  | import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.utils.* | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import kotlin.collections.ArrayList | import com.lagradost.cloudstream3.utils.loadExtractor | ||||||
| 
 | 
 | ||||||
| class PelisflixProvider:MainAPI() { | class PelisflixProvider : MainAPI() { | ||||||
|     override val mainUrl = "https://pelisflix.li" |     override val mainUrl = "https://pelisflix.li" | ||||||
|     override val name = "Pelisflix" |     override val name = "Pelisflix" | ||||||
|     override val lang = "es" |     override val lang = "es" | ||||||
|  | @ -16,6 +17,7 @@ class PelisflixProvider:MainAPI() { | ||||||
|         TvType.Movie, |         TvType.Movie, | ||||||
|         TvType.TvSeries, |         TvType.TvSeries, | ||||||
|     ) |     ) | ||||||
|  | 
 | ||||||
|     override suspend fun getMainPage(): HomePageResponse { |     override suspend fun getMainPage(): HomePageResponse { | ||||||
|         val items = ArrayList<HomePageList>() |         val items = ArrayList<HomePageList>() | ||||||
|         val urls = listOf( |         val urls = listOf( | ||||||
|  | @ -47,6 +49,7 @@ class PelisflixProvider:MainAPI() { | ||||||
|         if (items.size <= 0) throw ErrorLoadingException() |         if (items.size <= 0) throw ErrorLoadingException() | ||||||
|         return HomePageResponse(items) |         return HomePageResponse(items) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     override suspend fun search(query: String): List<SearchResponse> { |     override suspend fun search(query: String): List<SearchResponse> { | ||||||
|         val url = "$mainUrl/?s=$query" |         val url = "$mainUrl/?s=$query" | ||||||
|         val doc = app.get(url).document |         val doc = app.get(url).document | ||||||
|  | @ -93,17 +96,21 @@ class PelisflixProvider:MainAPI() { | ||||||
|             .replace(descRegex2, "").replace(descRegex3, "") |             .replace(descRegex2, "").replace(descRegex3, "") | ||||||
|             .replace(descRegex4, "").replace(descRegex5, "") |             .replace(descRegex4, "").replace(descRegex5, "") | ||||||
|         val desc2Regex = Regex("(G(e|é)nero:.*..)") |         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 = |         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() |                 ?.times(0)?.toInt() | ||||||
|         val year = document.selectFirst("span.Date")?.text() |         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 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 { |         val poster = try { | ||||||
|             posterRegex.findAll(postercss).map { |             posterRegex.findAll(postercss).map { | ||||||
|                 it.value.replace("\"og:image\" content=\"","") |                 it.value.replace("\"og:image\" content=\"", "") | ||||||
|             }.toList().first() |             }.toList().first() | ||||||
|         } catch (e: Exception) { |         } catch (e: Exception) { | ||||||
|             document.select(".TPostBg").attr("src") |             document.select(".TPostBg").attr("src") | ||||||
|  | @ -122,8 +129,8 @@ class PelisflixProvider:MainAPI() { | ||||||
| 
 | 
 | ||||||
|             val episodeList = ArrayList<TvSeriesEpisode>() |             val episodeList = ArrayList<TvSeriesEpisode>() | ||||||
| 
 | 
 | ||||||
|             for (season in list)  { |             for ((seasonInt, seasonUrl) in list) { | ||||||
|                 val seasonDocument = app.get(season.second).document |                 val seasonDocument = app.get(seasonUrl).document | ||||||
|                 val episodes = seasonDocument.select("table > tbody > tr") |                 val episodes = seasonDocument.select("table > tbody > tr") | ||||||
|                 if (episodes.isNotEmpty()) { |                 if (episodes.isNotEmpty()) { | ||||||
|                     episodes.forEach { episode -> |                     episodes.forEach { episode -> | ||||||
|  | @ -136,7 +143,7 @@ class PelisflixProvider:MainAPI() { | ||||||
|                         episodeList.add( |                         episodeList.add( | ||||||
|                             TvSeriesEpisode( |                             TvSeriesEpisode( | ||||||
|                                 name, |                                 name, | ||||||
|                                 season.first, |                                 seasonInt, | ||||||
|                                 epNum, |                                 epNum, | ||||||
|                                 href, |                                 href, | ||||||
|                                 fixUrlNull(epthumb), |                                 fixUrlNull(epthumb), | ||||||
|  | @ -160,7 +167,6 @@ class PelisflixProvider:MainAPI() { | ||||||
|                 rating |                 rating | ||||||
|             ) |             ) | ||||||
|         } else { |         } else { | ||||||
| 
 |  | ||||||
|             return newMovieLoadResponse( |             return newMovieLoadResponse( | ||||||
|                 title, |                 title, | ||||||
|                 url, |                 url, | ||||||
|  | @ -186,14 +192,17 @@ class PelisflixProvider:MainAPI() { | ||||||
|             val movieID = it.attr("data-id") |             val movieID = it.attr("data-id") | ||||||
|             val serverID = it.attr("data-key") |             val serverID = it.attr("data-key") | ||||||
|             val type = if (data.contains("pelicula")) 1 else 2 |             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 |             val doc1 = app.get(url).document | ||||||
|             doc1.select("div.Video iframe").apmap { |             doc1.select("div.Video iframe").apmap { | ||||||
|                 val iframe = it.attr("src") |                 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 |                 // djNIdHNCR2lKTGpnc3YwK3pyRCs3L2xkQmljSUZ4ai9ibTcza0JRODNMcmFIZ0hPejdlYW0yanJIL2prQ1JCZA POST KEY | ||||||
|                 app.post("https://pelisflix.li/stream/r.php", |                 app.post( | ||||||
|                     headers = mapOf("Host" to "pelisflix.li", |                     "https://pelisflix.li/stream/r.php", | ||||||
|  |                     headers = mapOf( | ||||||
|  |                         "Host" to "pelisflix.li", | ||||||
|                         "User-Agent" to USER_AGENT, |                         "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" 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", |                         "Accept-Language" to "en-US,en;q=0.5", | ||||||
|  | @ -208,12 +217,13 @@ class PelisflixProvider:MainAPI() { | ||||||
|                         "Sec-Fetch-User" to "?1", |                         "Sec-Fetch-User" to "?1", | ||||||
|                         "Pragma" to "no-cache", |                         "Pragma" to "no-cache", | ||||||
|                         "Cache-Control" to "no-cache", |                         "Cache-Control" to "no-cache", | ||||||
|                         "TE" to "trailers"), |                         "TE" to "trailers" | ||||||
|  |                     ), | ||||||
|                     params = mapOf(Pair("h", postkey)), |                     params = mapOf(Pair("h", postkey)), | ||||||
|                     data = mapOf(Pair("h", postkey)), |                     data = mapOf(Pair("h", postkey)), | ||||||
|                     allowRedirects = false |                     allowRedirects = false | ||||||
|                 ).response.headers.values("location").apmap { link -> |                 ).response.headers.values("location").apmap { link -> | ||||||
|                     val url1 = link.replace("#bu","") |                     val url1 = link.replace("#bu", "") | ||||||
|                     loadExtractor(url1, data, callback) |                     loadExtractor(url1, data, callback) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -1,11 +1,12 @@ | ||||||
| package com.lagradost.cloudstream3.movieproviders | package com.lagradost.cloudstream3.movieproviders | ||||||
| 
 | 
 | ||||||
| import com.lagradost.cloudstream3.* | import com.lagradost.cloudstream3.* | ||||||
|  | import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
| import com.lagradost.cloudstream3.utils.* | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import kotlin.collections.ArrayList | import com.lagradost.cloudstream3.utils.loadExtractor | ||||||
| 
 | 
 | ||||||
| class SeriesflixProvider:MainAPI() { | class SeriesflixProvider : MainAPI() { | ||||||
|     override val mainUrl = "https://seriesflix.video" |     override val mainUrl = "https://seriesflix.video" | ||||||
|     override val name = "Seriesflix" |     override val name = "Seriesflix" | ||||||
|     override val lang = "es" |     override val lang = "es" | ||||||
|  | @ -16,6 +17,7 @@ class SeriesflixProvider:MainAPI() { | ||||||
|         TvType.Movie, |         TvType.Movie, | ||||||
|         TvType.TvSeries, |         TvType.TvSeries, | ||||||
|     ) |     ) | ||||||
|  | 
 | ||||||
|     override suspend fun getMainPage(): HomePageResponse { |     override suspend fun getMainPage(): HomePageResponse { | ||||||
|         val items = ArrayList<HomePageList>() |         val items = ArrayList<HomePageList>() | ||||||
|         val urls = listOf( |         val urls = listOf( | ||||||
|  | @ -48,6 +50,7 @@ class SeriesflixProvider:MainAPI() { | ||||||
|         if (items.size <= 0) throw ErrorLoadingException() |         if (items.size <= 0) throw ErrorLoadingException() | ||||||
|         return HomePageResponse(items) |         return HomePageResponse(items) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     override suspend fun search(query: String): List<SearchResponse> { |     override suspend fun search(query: String): List<SearchResponse> { | ||||||
|         val url = "$mainUrl/?s=$query" |         val url = "$mainUrl/?s=$query" | ||||||
|         val doc = app.get(url).document |         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 type = if (url.contains("/movies/")) TvType.Movie else TvType.TvSeries | ||||||
| 
 | 
 | ||||||
|         val document = app.get(url).document |         val document = app.get(url).document | ||||||
| 
 | 
 | ||||||
|         val title = document.selectFirst("h1.Title").text() |         val title = document.selectFirst("h1.Title").text() | ||||||
|         val descRegex = Regex("(Recuerda.*Seriesflix.)") |         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 = |         val rating = | ||||||
|             document.selectFirst("div.Vote > div.post-ratings > span")?.text()?.toFloatOrNull() |             document.selectFirst("div.Vote > div.post-ratings > span")?.text()?.toFloatOrNull() | ||||||
|                 ?.times(1000)?.toInt() |                 ?.times(1000)?.toInt() | ||||||
|  | @ -100,10 +102,11 @@ class SeriesflixProvider:MainAPI() { | ||||||
|             null |             null | ||||||
|         } |         } | ||||||
|         val postercss = document.selectFirst("head").toString() |         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 { |         val poster = try { | ||||||
|             posterRegex.findAll(postercss).map { |             posterRegex.findAll(postercss).map { | ||||||
|                 it.value.replace("\"og:image\" content=\"","") |                 it.value.replace("\"og:image\" content=\"", "") | ||||||
|             }.toList().first() |             }.toList().first() | ||||||
|         } catch (e: Exception) { |         } catch (e: Exception) { | ||||||
|             document.select(".TPostBg").attr("src") |             document.select(".TPostBg").attr("src") | ||||||
|  | @ -186,14 +189,18 @@ class SeriesflixProvider:MainAPI() { | ||||||
|             val movieID = it.attr("data-id") |             val movieID = it.attr("data-id") | ||||||
|             val serverID = it.attr("data-key") |             val serverID = it.attr("data-key") | ||||||
|             val type = if (data.contains("movies")) 1 else 2 |             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 |             val doc1 = app.get(url).document | ||||||
|             doc1.select("div.Video iframe").apmap { |             doc1.select("div.Video iframe").apmap { | ||||||
|                 val iframe = it.attr("src") |                 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 |                 // djNIdHNCR2lKTGpnc3YwK3pyRCs3L2xkQmljSUZ4ai9ibTcza0JRODNMcmFIZ0hPejdlYW0yanJIL2prQ1JCZA POST KEY | ||||||
|                 app.post("https://sc.seriesflix.video/r.php", |                 app.post( | ||||||
|                     headers = mapOf("Host" to "sc.seriesflix.video", |                     "https://sc.seriesflix.video/r.php", | ||||||
|  |                     headers = mapOf( | ||||||
|  |                         "Host" to "sc.seriesflix.video", | ||||||
|                         "User-Agent" to USER_AGENT, |                         "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" 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", |                         "Accept-Language" to "en-US,en;q=0.5", | ||||||
|  | @ -206,12 +213,13 @@ class SeriesflixProvider:MainAPI() { | ||||||
|                         "Sec-Fetch-Dest" to "iframe", |                         "Sec-Fetch-Dest" to "iframe", | ||||||
|                         "Sec-Fetch-Mode" to "navigate", |                         "Sec-Fetch-Mode" to "navigate", | ||||||
|                         "Sec-Fetch-Site" to "same-origin", |                         "Sec-Fetch-Site" to "same-origin", | ||||||
|                         "Sec-Fetch-User" to "?1",), |                         "Sec-Fetch-User" to "?1", | ||||||
|  |                     ), | ||||||
|                     params = mapOf(Pair("h", postkey)), |                     params = mapOf(Pair("h", postkey)), | ||||||
|                     data = mapOf(Pair("h", postkey)), |                     data = mapOf(Pair("h", postkey)), | ||||||
|                     allowRedirects = false |                     allowRedirects = false | ||||||
|                 ).response.headers.values("location").apmap {link -> |                 ).response.headers.values("location").apmap { link -> | ||||||
|                     val url1 = link.replace("#bu","") |                     val url1 = link.replace("#bu", "") | ||||||
|                     loadExtractor(url1, data, callback) |                     loadExtractor(url1, data, callback) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -2,6 +2,8 @@ package com.lagradost.cloudstream3.movieproviders | ||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.annotation.JsonProperty | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
| import com.lagradost.cloudstream3.* | 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.network.WebViewResolver | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.parseJson | import com.lagradost.cloudstream3.utils.AppUtils.parseJson | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.toJson | 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 img = details.select("img.film-poster-img") | ||||||
|         val posterUrl = img.attr("src") |         val posterUrl = img.attr("src") | ||||||
|         val title = img.attr("title") |         val title = img.attr("title") | ||||||
|  | 
 | ||||||
|  |         /* | ||||||
|         val year = Regex("""[Rr]eleased:\s*(\d{4})""").find( |         val year = Regex("""[Rr]eleased:\s*(\d{4})""").find( | ||||||
|             document.select("div.elements").text() |             document.select("div.elements").text() | ||||||
|         )?.groupValues?.get(1)?.toIntOrNull() |         )?.groupValues?.get(1)?.toIntOrNull() | ||||||
|         val duration = Regex("""[Dd]uration:\s*(\d*)""").find( |         val duration = Regex("""[Dd]uration:\s*(\d*)""").find( | ||||||
|             document.select("div.elements").text() |             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 plot = details.select("div.description").text().replace("Overview:", "").trim() | ||||||
| 
 | 
 | ||||||
|         val isMovie = url.contains("/movie/") |         val isMovie = url.contains("/movie/") | ||||||
|  | @ -156,6 +182,8 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { | ||||||
|                 this.posterUrl = posterUrl |                 this.posterUrl = posterUrl | ||||||
|                 this.plot = plot |                 this.plot = plot | ||||||
|                 setDuration(duration) |                 setDuration(duration) | ||||||
|  |                 setActorNames(cast) | ||||||
|  |                 this.tags = tags | ||||||
|                 this.recommendations = recommendations |                 this.recommendations = recommendations | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|  | @ -201,6 +229,8 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() { | ||||||
|                 this.year = year |                 this.year = year | ||||||
|                 this.plot = plot |                 this.plot = plot | ||||||
|                 setDuration(duration) |                 setDuration(duration) | ||||||
|  |                 setActorNames(cast) | ||||||
|  |                 this.tags = tags | ||||||
|                 this.recommendations = recommendations |                 this.recommendations = recommendations | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -779,10 +779,10 @@ class HomeFragment : Fragment() { | ||||||
|         home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { view, _, scrollY, _, oldScrollY -> |         home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { view, _, scrollY, _, oldScrollY -> | ||||||
|             val dy = scrollY - oldScrollY |             val dy = scrollY - oldScrollY | ||||||
|             if (dy > 0) { //check for scroll down |             if (dy > 0) { //check for scroll down | ||||||
|                 home_api_fab?.hide() |                 home_api_fab?.shrink() // hide | ||||||
|             } else if (dy < -5) { |             } else if (dy < -5) { | ||||||
|                 if (view?.context?.isTvSettings() == false) { |                 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 |         //requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR | ||||||
|         downloadButton?.dispose() |         downloadButton?.dispose() | ||||||
|         updateUIListener = null |         updateUIListener = null | ||||||
|  |         result_cast_items?.let { | ||||||
|  |             PanelsChildGestureRegionObserver.Provider.get().unregister(it) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         super.onDestroy() |         super.onDestroy() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -482,6 +486,22 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|         setFormatText(result_meta_duration, R.string.duration_format, duration) |         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?) { |     private fun setYear(year: Int?) { | ||||||
|         setFormatText(result_meta_year, R.string.year_format, year) |         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)) |         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>?) { |     private fun setRecommendations(rec: List<SearchResponse>?) { | ||||||
|         val isInvalid = rec.isNullOrEmpty() |         val isInvalid = rec.isNullOrEmpty() | ||||||
|         result_recommendations?.isGone = isInvalid |         result_recommendations?.isGone = isInvalid | ||||||
|  | @ -540,6 +581,10 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
| 
 | 
 | ||||||
|  |         result_cast_items?.let { | ||||||
|  |             PanelsChildGestureRegionObserver.Provider.get().register(it) | ||||||
|  |         } | ||||||
|  |         result_cast_items?.adapter = ActorAdaptor(mutableListOf()) | ||||||
|         fixGrid() |         fixGrid() | ||||||
|         result_recommendations?.spanCount = 3 |         result_recommendations?.spanCount = 3 | ||||||
|         result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE) |         result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE) | ||||||
|  | @ -1281,22 +1326,17 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         val metadataInfoArray = ArrayList<Pair<Int, String>>() |                         val showStatus = when (d) { | ||||||
|                         if (d is AnimeLoadResponse) { |                             is TvSeriesLoadResponse -> d.showStatus | ||||||
|                             val status = when (d.showStatus) { |                             is AnimeLoadResponse -> d.showStatus | ||||||
|                                 null -> null |                             else -> null | ||||||
|                                 ShowStatus.Ongoing -> R.string.status_ongoing |  | ||||||
|                                 ShowStatus.Completed -> R.string.status_completed |  | ||||||
|                         } |                         } | ||||||
|                             if (status != null) { |                         setShow(showStatus) | ||||||
|                                 metadataInfoArray.add(Pair(R.string.status, getString(status))) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         setDuration(d.duration) |                         setDuration(d.duration) | ||||||
|                         setYear(d.year) |                         setYear(d.year) | ||||||
|                         setRating(d.rating) |                         setRating(d.rating) | ||||||
|                         setRecommendations(d.recommendations) |                         setRecommendations(d.recommendations) | ||||||
|  |                         setActors(d.actors) | ||||||
| 
 | 
 | ||||||
|                         result_meta_site?.text = d.apiName |                         result_meta_site?.text = d.apiName | ||||||
| 
 | 
 | ||||||
|  | @ -1509,7 +1549,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
| 
 | 
 | ||||||
|             val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) |             val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) | ||||||
|             val showFillers = |             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 |             val tempUrl = url | ||||||
|             if (tempUrl != null) { |             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>) { |     override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package com.lagradost.cloudstream3.utils | package com.lagradost.cloudstream3.utils | ||||||
| 
 | 
 | ||||||
|  | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.getKey | import com.lagradost.cloudstream3.AcraApplication.Companion.getKey | ||||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys | import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys | ||||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey | import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey | ||||||
|  | @ -17,7 +18,10 @@ const val RESULT_SEASON = "result_season" | ||||||
| const val RESULT_DUB = "result_dub" | const val RESULT_DUB = "result_dub" | ||||||
| 
 | 
 | ||||||
| object DataStoreHelper { | 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 { |     fun PosDur.fixVisual(): PosDur { | ||||||
|         if (duration <= 0) return PosDur(0, duration) |         if (duration <= 0) return PosDur(0, duration) | ||||||
|  | @ -29,31 +33,31 @@ object DataStoreHelper { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     data class BookmarkedData( |     data class BookmarkedData( | ||||||
|         override val id: Int?, |        @JsonProperty("id") override val id: Int?, | ||||||
|         val bookmarkedTime: Long, |        @JsonProperty("bookmarkedTime") val bookmarkedTime: Long, | ||||||
|         val latestUpdatedTime: Long, |        @JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long, | ||||||
|         override val name: String, |        @JsonProperty("name") override val name: String, | ||||||
|         override val url: String, |        @JsonProperty("url") override val url: String, | ||||||
|         override val apiName: String, |        @JsonProperty("apiName") override val apiName: String, | ||||||
|         override val type: TvType, |        @JsonProperty("type") override val type: TvType, | ||||||
|         override val posterUrl: String?, |        @JsonProperty("posterUrl") override val posterUrl: String?, | ||||||
|         val year: Int?, |        @JsonProperty("year") val year: Int?, | ||||||
|     ) : SearchResponse |     ) : SearchResponse | ||||||
| 
 | 
 | ||||||
|     data class ResumeWatchingResult( |     data class ResumeWatchingResult( | ||||||
|         override val name: String, |         @JsonProperty("name") override val name: String, | ||||||
|         override val url: String, |         @JsonProperty("url") override val url: String, | ||||||
|         override val apiName: String, |         @JsonProperty("apiName") override val apiName: String, | ||||||
|         override val type: TvType, |         @JsonProperty("type") override val type: TvType, | ||||||
|         override val posterUrl: String?, |         @JsonProperty("posterUrl") override val posterUrl: String?, | ||||||
| 
 | 
 | ||||||
|         val watchPos: PosDur?, |         @JsonProperty("watchPos") val watchPos: PosDur?, | ||||||
| 
 | 
 | ||||||
|         override val id: Int?, |         @JsonProperty("id") override val id: Int?, | ||||||
|         val parentId: Int?, |         @JsonProperty("parentId") val parentId: Int?, | ||||||
|         val episode: Int?, |         @JsonProperty("episode") val episode: Int?, | ||||||
|         val season: Int?, |         @JsonProperty("season") val season: Int?, | ||||||
|         val isFromDownload: Boolean, |         @JsonProperty("isFromDownload") val isFromDownload: Boolean, | ||||||
|     ) : SearchResponse |     ) : SearchResponse | ||||||
| 
 | 
 | ||||||
|     var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION |     var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION | ||||||
|  | @ -124,7 +128,7 @@ object DataStoreHelper { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun getViewPos(id: Int?): PosDur? { |     fun getViewPos(id: Int?): PosDur? { | ||||||
|         if(id == null) return null |         if (id == null) return null | ||||||
|         return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) |         return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -148,7 +152,13 @@ object DataStoreHelper { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun getResultWatchState(id: Int): WatchType { |     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 { |     fun getResultSeason(id: Int): Int { | ||||||
|  |  | ||||||
|  | @ -1,37 +1,38 @@ | ||||||
| package com.lagradost.cloudstream3.utils | package com.lagradost.cloudstream3.utils | ||||||
| 
 | 
 | ||||||
|  | import com.fasterxml.jackson.annotation.JsonProperty | ||||||
| import com.lagradost.cloudstream3.TvType | import com.lagradost.cloudstream3.TvType | ||||||
| import com.lagradost.cloudstream3.ui.download.EasyDownloadButton | import com.lagradost.cloudstream3.ui.download.EasyDownloadButton | ||||||
| 
 | 
 | ||||||
| object VideoDownloadHelper { | object VideoDownloadHelper { | ||||||
|     data class DownloadEpisodeCached( |     data class DownloadEpisodeCached( | ||||||
|         val name: String?, |         @JsonProperty("name") val name: String?, | ||||||
|         val poster: String?, |         @JsonProperty("poster") val poster: String?, | ||||||
|         val episode: Int, |         @JsonProperty("episode") val episode: Int, | ||||||
|         val season: Int?, |         @JsonProperty("season") val season: Int?, | ||||||
|         override val id: Int, |         @JsonProperty("id") override val id: Int, | ||||||
|         val parentId: Int, |         @JsonProperty("parentId") val parentId: Int, | ||||||
|         val rating: Int?, |         @JsonProperty("rating") val rating: Int?, | ||||||
|         val description: String?, |         @JsonProperty("description") val description: String?, | ||||||
|         val cacheTime: Long, |         @JsonProperty("cacheTime") val cacheTime: Long, | ||||||
|     ) : EasyDownloadButton.IMinimumData |     ) : EasyDownloadButton.IMinimumData | ||||||
| 
 | 
 | ||||||
|     data class DownloadHeaderCached( |     data class DownloadHeaderCached( | ||||||
|         val apiName: String, |         @JsonProperty("apiName") val apiName: String, | ||||||
|         val url: String, |         @JsonProperty("url") val url: String, | ||||||
|         val type: TvType, |         @JsonProperty("type") val type: TvType, | ||||||
|         val name: String, |         @JsonProperty("name") val name: String, | ||||||
|         val poster: String?, |         @JsonProperty("poster") val poster: String?, | ||||||
|         val id: Int, |         @JsonProperty("id") val id: Int, | ||||||
|         val cacheTime: Long, |         @JsonProperty("cacheTime") val cacheTime: Long, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     data class ResumeWatching( |     data class ResumeWatching( | ||||||
|         val parentId: Int, |         @JsonProperty("parentId") val parentId: Int, | ||||||
|         val episodeId: Int, |         @JsonProperty("episodeId") val episodeId: Int, | ||||||
|         val episode: Int?, |         @JsonProperty("episode") val episode: Int?, | ||||||
|         val season: Int?, |         @JsonProperty("season") val season: Int?, | ||||||
|         val updateTime : Long, |         @JsonProperty("updateTime") val updateTime: Long, | ||||||
|         val isFromDownload: Boolean, |         @JsonProperty("isFromDownload") val isFromDownload: Boolean, | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  | @ -108,44 +108,44 @@ object VideoDownloadManager { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     data class DownloadEpisodeMetadata( |     data class DownloadEpisodeMetadata( | ||||||
|         val id: Int, |         @JsonProperty("id") val id: Int, | ||||||
|         val mainName: String, |         @JsonProperty("mainName") val mainName: String, | ||||||
|         val sourceApiName: String?, |         @JsonProperty("sourceApiName") val sourceApiName: String?, | ||||||
|         val poster: String?, |         @JsonProperty("poster") val poster: String?, | ||||||
|         val name: String?, |         @JsonProperty("name") val name: String?, | ||||||
|         val season: Int?, |         @JsonProperty("season") val season: Int?, | ||||||
|         val episode: Int? |         @JsonProperty("episode") val episode: Int? | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     data class DownloadItem( |     data class DownloadItem( | ||||||
|         val source: String?, |         @JsonProperty("source") val source: String?, | ||||||
|         val folder: String?, |         @JsonProperty("folder") val folder: String?, | ||||||
|         val ep: DownloadEpisodeMetadata, |         @JsonProperty("ep") val ep: DownloadEpisodeMetadata, | ||||||
|         val links: List<ExtractorLink>, |         @JsonProperty("links") val links: List<ExtractorLink>, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     data class DownloadResumePackage( |     data class DownloadResumePackage( | ||||||
|         val item: DownloadItem, |         @JsonProperty("item") val item: DownloadItem, | ||||||
|         val linkIndex: Int?, |         @JsonProperty("linkIndex") val linkIndex: Int?, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     data class DownloadedFileInfo( |     data class DownloadedFileInfo( | ||||||
|         val totalBytes: Long, |         @JsonProperty("totalBytes") val totalBytes: Long, | ||||||
|         val relativePath: String, |         @JsonProperty("relativePath") val relativePath: String, | ||||||
|         val displayName: String, |         @JsonProperty("displayName") val displayName: String, | ||||||
|         val extraInfo: String? = null, |         @JsonProperty("extraInfo") val extraInfo: String? = null, | ||||||
|         val basePath: String? = null // null is for legacy downloads. See getDefaultPath() |         @JsonProperty("basePath") val basePath: String? = null // null is for legacy downloads. See getDefaultPath() | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     data class DownloadedFileInfoResult( |     data class DownloadedFileInfoResult( | ||||||
|         val fileLength: Long, |         @JsonProperty("fileLength") val fileLength: Long, | ||||||
|         val totalBytes: Long, |         @JsonProperty("totalBytes") val totalBytes: Long, | ||||||
|         val path: Uri, |         @JsonProperty("path") val path: Uri, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     data class DownloadQueueResumePackage( |     data class DownloadQueueResumePackage( | ||||||
|         val index: Int, |         @JsonProperty("index") val index: Int, | ||||||
|         val pkg: DownloadResumePackage, |         @JsonProperty("pkg") val pkg: DownloadResumePackage, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     private const val SUCCESS_DOWNLOAD_DONE = 1 |     private const val SUCCESS_DOWNLOAD_DONE = 1 | ||||||
|  |  | ||||||
|  | @ -8,10 +8,19 @@ import com.lagradost.cloudstream3.R | ||||||
| import kotlin.math.max | import kotlin.math.max | ||||||
| 
 | 
 | ||||||
| class FlowLayout : ViewGroup { | class FlowLayout : ViewGroup { | ||||||
|  |     var itemSpacing : Int = 0 | ||||||
|  | 
 | ||||||
|     constructor(context: Context?) : super(context) |     constructor(context: Context?) : super(context) | ||||||
| 
 | 
 | ||||||
|     @JvmOverloads |     //@JvmOverloads | ||||||
|     constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) |     //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) { |     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | ||||||
|         val realWidth = MeasureSpec.getSize(widthMeasureSpec) |         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 |             //check if child can be placed in the current row, else go to next line | ||||||
|             if (currentChildHookPointx + childWidth > realWidth) { |             if (currentChildHookPointx + childWidth > realWidth) { | ||||||
|                 //new line |                 //new line | ||||||
|                 currentWidth = Math.max(currentWidth, currentChildHookPointx) |                 currentWidth = max(currentWidth, currentChildHookPointx) | ||||||
| 
 | 
 | ||||||
|                 //reset for new line |                 //reset for new line | ||||||
|                 currentChildHookPointx = 0 |                 currentChildHookPointx = 0 | ||||||
|                 currentChildHookPointy += childHeight |                 currentChildHookPointy += childHeight | ||||||
|             } |             } | ||||||
|             val nextChildHookPointx = currentChildHookPointx + childWidth |             val nextChildHookPointx = currentChildHookPointx + childWidth + if(childWidth == 0) 0 else itemSpacing | ||||||
|             val nextChildHookPointy = currentChildHookPointy |             val nextChildHookPointy = currentChildHookPointy | ||||||
|             currentHeight = max(currentHeight, currentChildHookPointy + childHeight) |             currentHeight = max(currentHeight, currentChildHookPointy + childHeight) | ||||||
|             val lp = child.layoutParams as LayoutParams |             val lp = child.layoutParams as LayoutParams | ||||||
|  | @ -44,7 +53,7 @@ class FlowLayout : ViewGroup { | ||||||
|             currentChildHookPointx = nextChildHookPointx |             currentChildHookPointx = nextChildHookPointx | ||||||
|             currentChildHookPointy = nextChildHookPointy |             currentChildHookPointy = nextChildHookPointy | ||||||
|         } |         } | ||||||
|         currentWidth = Math.max(currentChildHookPointx, currentWidth) |         currentWidth = max(currentChildHookPointx, currentWidth) | ||||||
|         setMeasuredDimension(resolveSize(currentWidth, widthMeasureSpec), |         setMeasuredDimension(resolveSize(currentWidth, widthMeasureSpec), | ||||||
|             resolveSize(currentHeight, heightMeasureSpec)) |             resolveSize(currentHeight, heightMeasureSpec)) | ||||||
|     } |     } | ||||||
|  | @ -83,7 +92,7 @@ class FlowLayout : ViewGroup { | ||||||
|         @SuppressLint("CustomViewStyleable") |         @SuppressLint("CustomViewStyleable") | ||||||
|         internal constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) { |         internal constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) { | ||||||
|             val t = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout) |             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() |             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 |     <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton | ||||||
|             android:visibility="gone" |             android:visibility="gone" | ||||||
|             tools:visibility="visible" |             tools:visibility="visible" | ||||||
|  |             android:text="@string/home_source" | ||||||
|             android:id="@+id/home_api_fab" |             android:id="@+id/home_api_fab" | ||||||
|             app:icon="@drawable/ic_baseline_filter_list_24" |             app:icon="@drawable/ic_baseline_filter_list_24" | ||||||
|             style="@style/ExtendedFloatingActionButton" |             style="@style/ExtendedFloatingActionButton" | ||||||
|  |             android:textColor="?attr/textColor" | ||||||
|             tools:ignore="ContentDescription" /> |             tools:ignore="ContentDescription" /> | ||||||
| </FrameLayout> | </FrameLayout> | ||||||
|  | @ -311,6 +311,7 @@ | ||||||
|                                     android:layout_height="wrap_content" /> |                                     android:layout_height="wrap_content" /> | ||||||
| 
 | 
 | ||||||
|                             <com.lagradost.cloudstream3.widget.FlowLayout |                             <com.lagradost.cloudstream3.widget.FlowLayout | ||||||
|  |                                     app:itemSpacing="10dp" | ||||||
|                                     android:layout_width="match_parent" |                                     android:layout_width="match_parent" | ||||||
|                                     android:layout_height="wrap_content"> |                                     android:layout_height="wrap_content"> | ||||||
| 
 | 
 | ||||||
|  | @ -322,56 +323,28 @@ | ||||||
| 
 | 
 | ||||||
|                                 <TextView |                                 <TextView | ||||||
|                                         android:id="@+id/result_meta_type" |                                         android:id="@+id/result_meta_type" | ||||||
|                                         android:paddingStart="5dp" |                                         style="@style/ResultInfoText" | ||||||
|                                         android:paddingEnd="5dp" |                                         tools:text="Movie" /> | ||||||
|                                         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" /> |  | ||||||
| 
 | 
 | ||||||
|                                 <TextView |                                 <TextView | ||||||
|                                         android:paddingStart="5dp" |                                         tools:text="2022" | ||||||
|                                         android:paddingEnd="5dp" |  | ||||||
|                                         android:minHeight="24dp" |  | ||||||
|                                         android:gravity="center" |  | ||||||
| 
 |  | ||||||
|                                         android:id="@+id/result_meta_year" |                                         android:id="@+id/result_meta_year" | ||||||
|                                         android:layout_marginStart="10dp" |                                         style="@style/ResultInfoText" /> | ||||||
|                                         tools:text="2021" |  | ||||||
|                                         android:layout_gravity="center_vertical" |  | ||||||
|                                         android:textColor="?attr/white" |  | ||||||
|                                         android:layout_width="wrap_content" |  | ||||||
|                                         android:layout_height="wrap_content" /> |  | ||||||
| 
 | 
 | ||||||
|                                 <TextView |                                 <TextView | ||||||
|                                         android:id="@+id/result_meta_rating" |                                         android:id="@+id/result_meta_rating" | ||||||
|                                         android:paddingStart="5dp" |                                         style="@style/ResultInfoText" | ||||||
|                                         android:paddingEnd="5dp" |                                         tools:text="Rated: 8.5/10.0" /> | ||||||
|                                         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" /> |  | ||||||
| 
 | 
 | ||||||
|                                 <TextView |                                 <TextView | ||||||
|                                         android:paddingStart="5dp" |                                         android:id="@+id/result_meta_status" | ||||||
|                                         android:paddingEnd="5dp" |                                         style="@style/ResultInfoText" | ||||||
|                                         android:minHeight="24dp" |                                         tools:text="Ongoing" /> | ||||||
|                                         android:gravity="center" |  | ||||||
|                                         android:id="@+id/result_meta_duration" |  | ||||||
| 
 | 
 | ||||||
|                                         tools:text="121min" |                                 <TextView | ||||||
|                                         android:layout_gravity="center_vertical" |                                         style="@style/ResultInfoText" | ||||||
|                                         android:textColor="?attr/textColor" |                                         android:id="@+id/result_meta_duration" | ||||||
|                                         android:layout_width="wrap_content" |                                         tools:text="121min" /> | ||||||
|                                         android:layout_height="wrap_content" /> |  | ||||||
|                             </com.lagradost.cloudstream3.widget.FlowLayout> |                             </com.lagradost.cloudstream3.widget.FlowLayout> | ||||||
| 
 | 
 | ||||||
|                             <FrameLayout |                             <FrameLayout | ||||||
|  | @ -428,6 +401,28 @@ | ||||||
|                         <requestFocus /> |                         <requestFocus /> | ||||||
|                     </com.google.android.material.button.MaterialButton> |                     </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 |                     <TextView | ||||||
|                             android:textColor="?attr/grayTextColor" |                             android:textColor="?attr/grayTextColor" | ||||||
|                             android:id="@+id/result_vpn" |                             android:id="@+id/result_vpn" | ||||||
|  | @ -564,6 +559,7 @@ | ||||||
|                                 android:text="@string/resume" |                                 android:text="@string/resume" | ||||||
|                                 app:icon="@drawable/ic_baseline_play_arrow_24" |                                 app:icon="@drawable/ic_baseline_play_arrow_24" | ||||||
|                                 android:layout_width="match_parent" /> |                                 android:layout_width="match_parent" /> | ||||||
|  | 
 | ||||||
|                         <com.google.android.material.button.MaterialButton |                         <com.google.android.material.button.MaterialButton | ||||||
|                                 android:layout_marginBottom="10dp" |                                 android:layout_marginBottom="10dp" | ||||||
|                                 android:nextFocusUp="@id/result_bookmark_button" |                                 android:nextFocusUp="@id/result_bookmark_button" | ||||||
|  | @ -607,6 +603,7 @@ | ||||||
|                                     android:layout_weight="1" |                                     android:layout_weight="1" | ||||||
|                                     android:visibility="visible" |                                     android:visibility="visible" | ||||||
|                                     tools:visibility="visible" /> |                                     tools:visibility="visible" /> | ||||||
|  | 
 | ||||||
|                             <TextView |                             <TextView | ||||||
|                                     android:id="@+id/result_resume_series_progress_text" |                                     android:id="@+id/result_resume_series_progress_text" | ||||||
|                                     android:layout_gravity="center" |                                     android:layout_gravity="center" | ||||||
|  |  | ||||||
|  | @ -1,10 +1,12 @@ | ||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <resources> | <resources> | ||||||
|     <declare-styleable name="FlowLayout_Layout"/> |     <declare-styleable name="FlowLayout_Layout"> | ||||||
|     <declare-styleable name="FlowLayout_Layout_layout_space"/> |         <attr format="dimension" name="itemSpacing" /> | ||||||
|  |     </declare-styleable> | ||||||
|  |     <declare-styleable name="FlowLayout_Layout_layout_space" /> | ||||||
| 
 | 
 | ||||||
|     <declare-styleable name="CustomCast"> |     <declare-styleable name="CustomCast"> | ||||||
|         <attr name="customCastBackgroundColor" format="color"/> |         <attr name="customCastBackgroundColor" format="color" /> | ||||||
|     </declare-styleable> |     </declare-styleable> | ||||||
| 
 | 
 | ||||||
|     <style name="customCastDefColor"> |     <style name="customCastDefColor"> | ||||||
|  | @ -12,19 +14,19 @@ | ||||||
|     </style> |     </style> | ||||||
| 
 | 
 | ||||||
|     <declare-styleable name="MainColors"> |     <declare-styleable name="MainColors"> | ||||||
|         <attr name="colorPrimary" format="color"/> |         <attr name="colorPrimary" format="color" /> | ||||||
|         <attr name="colorSearch" format="color"/> |         <attr name="colorSearch" format="color" /> | ||||||
|         <attr name="colorOngoing" format="color"/> |         <attr name="colorOngoing" format="color" /> | ||||||
|         <attr name="colorPrimaryDark" format="color"/> |         <attr name="colorPrimaryDark" format="color" /> | ||||||
| 
 | 
 | ||||||
|         <attr name="primaryGrayBackground" format="color"/> |         <attr name="primaryGrayBackground" format="color" /> | ||||||
|         <attr name="primaryBlackBackground" format="color"/> |         <attr name="primaryBlackBackground" format="color" /> | ||||||
|         <attr name="iconGrayBackground" format="color"/> |         <attr name="iconGrayBackground" format="color" /> | ||||||
|         <attr name="boxItemBackground" format="color"/> |         <attr name="boxItemBackground" format="color" /> | ||||||
| 
 | 
 | ||||||
|         <attr name="textColor" format="color"/> |         <attr name="textColor" format="color" /> | ||||||
|         <attr name="grayTextColor" format="color"/> |         <attr name="grayTextColor" format="color" /> | ||||||
|         <attr name="iconColor" format="color"/> |         <attr name="iconColor" format="color" /> | ||||||
|         <attr name="white" format="color"/> |         <attr name="white" format="color" /> | ||||||
|     </declare-styleable> |     </declare-styleable> | ||||||
| </resources> | </resources> | ||||||
|  | @ -46,6 +46,7 @@ | ||||||
|     <string name="rating_format" translatable="false" formatted="true">%.1f/10.0</string> |     <string name="rating_format" translatable="false" formatted="true">%.1f/10.0</string> | ||||||
|     <string name="year_format" translatable="false" formatted="true">%d</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="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 --> |     <!-- 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> |     <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_loaded_subtitles" formatted="true">Loaded %s</string> | ||||||
|     <string name="player_load_subtitles">Load from file</string> |     <string name="player_load_subtitles">Load from file</string> | ||||||
|     <string name="downloaded_file">Downloaded 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> | </resources> | ||||||
|  |  | ||||||
|  | @ -229,6 +229,19 @@ | ||||||
|         <item name="android:fontFamily">@font/google_sans</item> |         <item name="android:fontFamily">@font/google_sans</item> | ||||||
|     </style> |     </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"> |     <style name="AppMaterialButtonStyle" parent="Widget.MaterialComponents.Button"> | ||||||
|         <item name="android:fontFamily">@font/google_sans</item> |         <item name="android:fontFamily">@font/google_sans</item> | ||||||
|     </style> |     </style> | ||||||
|  |  | ||||||
|  | @ -95,7 +95,7 @@ | ||||||
|                 android:key="@string/show_fillers_key" |                 android:key="@string/show_fillers_key" | ||||||
|                 android:icon="@drawable/ic_baseline_skip_next_24" |                 android:icon="@drawable/ic_baseline_skip_next_24" | ||||||
|                 android:title="@string/show_fillers_settings" |                 android:title="@string/show_fillers_settings" | ||||||
|                 android:defaultValue="true" /> |                 android:defaultValue="false" /> | ||||||
|         <Preference |         <Preference | ||||||
|                 android:key="@string/dns_key" |                 android:key="@string/dns_key" | ||||||
|                 android:title="@string/dns_pref" |                 android:title="@string/dns_pref" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue