mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
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…
Reference in a new issue