cast + minify (json fix) + ongoing + UI change

This commit is contained in:
LagradOst 2022-02-05 23:21:45 +01:00
parent bd14fad607
commit 91be244d61
24 changed files with 731 additions and 270 deletions

View File

@ -36,7 +36,7 @@ android {
targetSdkVersion 30
versionCode 42
versionName "2.6.9"
versionName "2.6.10"
resValue "string", "app_version",
"${defaultConfig.versionName}${versionNameSuffix ?: ""}"
@ -52,8 +52,9 @@ android {
buildTypes {
release {
minifyEnabled false
debuggable true
debuggable false
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
prerelease {
@ -61,12 +62,15 @@ android {
buildConfigField("boolean", "BETA", "true")
signingConfig signingConfigs.prerelease
versionNameSuffix '-PRE'
minifyEnabled false
debuggable true
minifyEnabled true
debuggable false
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
applicationIdSuffix ".debug"
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {

View File

@ -189,12 +189,13 @@ object APIHolder {
return realSet
}
fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired : Boolean = true): List<MainAPI> {
fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List<MainAPI> {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val currentPrefMedia =
settingsManager.getInt(this.getString(R.string.prefer_media_type_key), 0)
val langs = this.getApiProviderLangSettings()
val allApis = apis.filter { langs.contains(it.lang) }.filter { api -> api.hasMainPage || !hasHomePageIsRequired}
val allApis = apis.filter { langs.contains(it.lang) }
.filter { api -> api.hasMainPage || !hasHomePageIsRequired }
return if (currentPrefMedia < 1) {
allApis
} else {
@ -426,6 +427,23 @@ interface SearchResponse {
val id: Int?
}
enum class ActorRole {
Main,
Supporting,
}
data class Actor(
val name: String,
val image: String? = null,
)
data class ActorData(
val actor: Actor,
val role: ActorRole? = null,
val roleString : String? = null,
val voiceActor: Actor? = null,
)
data class AnimeSearchResponse(
override val name: String,
override val url: String,
@ -488,6 +506,38 @@ interface LoadResponse {
var duration: Int? // in minutes
val trailerUrl: String?
val recommendations: List<SearchResponse>?
var actors: List<ActorData>?
companion object {
fun LoadResponse.setActorNames(actors: List<String>?) {
this.actors = actors?.map { ActorData(Actor(it)) }
}
fun LoadResponse.setActors(actors: List<Pair<Actor, String?>>?) {
println("ACTORS: ${actors?.size}")
this.actors = actors?.map { (actor, role) -> ActorData(actor, roleString = role) }
}
fun LoadResponse.setDuration(input: String?) {
val cleanInput = input?.trim()?.replace(" ","") ?: return
Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
if (values.size == 3) {
val hours = values[1].toIntOrNull()
val minutes = values[2].toIntOrNull()
this.duration = if (minutes != null && hours != null) {
hours * 60 + minutes
} else null
if (this.duration != null) return
}
}
Regex("([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
if (values.size == 2) {
this.duration = values[1].toIntOrNull()
if (this.duration != null) return
}
}
}
}
}
fun LoadResponse?.isEpisodeBased(): Boolean {
@ -530,6 +580,7 @@ data class TorrentLoadResponse(
override var duration: Int? = null,
override var trailerUrl: String? = null,
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
) : LoadResponse
data class AnimeLoadResponse(
@ -556,6 +607,7 @@ data class AnimeLoadResponse(
override var duration: Int? = null,
override var trailerUrl: String? = null,
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
) : LoadResponse
fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<AnimeEpisode>?) {
@ -591,6 +643,7 @@ data class MovieLoadResponse(
override var duration: Int? = null,
override var trailerUrl: String? = null,
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
) : LoadResponse
fun MainAPI.newMovieLoadResponse(
@ -611,24 +664,6 @@ fun MainAPI.newMovieLoadResponse(
return builder
}
fun LoadResponse.setDuration(input: String?) {
if (input == null) return
Regex("([0-9]*)h.*?([0-9]*)m").matchEntire(input)?.groupValues?.let { values ->
if (values.size == 3) {
val hours = values[1].toIntOrNull()
val minutes = values[2].toIntOrNull()
this.duration = if (minutes != null && hours != null) {
hours * 60 + minutes
} else null
}
}
Regex("([0-9]*)m").matchEntire(input)?.groupValues?.let { values ->
if (values.size == 2) {
this.duration = values[1].toIntOrNull()
}
}
}
data class TvSeriesEpisode(
val name: String? = null,
val season: Int? = null,
@ -658,6 +693,7 @@ data class TvSeriesLoadResponse(
override var duration: Int? = null,
override var trailerUrl: String? = null,
override var recommendations: List<SearchResponse>? = null,
override var actors: List<ActorData>? = null,
) : LoadResponse
fun MainAPI.newTvSeriesLoadResponse(
@ -682,6 +718,7 @@ fun fetchUrls(text: String?): List<String> {
if (text.isNullOrEmpty()) {
return listOf()
}
val linkRegex = Regex("""(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*))""")
val linkRegex =
Regex("""(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*))""")
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}

View File

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.animeproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.ExtractorLink
@ -218,18 +219,18 @@ class GogoanimeProvider : MainAPI() {
}
data class GogoSources(
val source: List<GogoSource>?,
val sourceBk: List<GogoSource>?,
@JsonProperty("source") val source: List<GogoSource>?,
@JsonProperty("sourceBk") val sourceBk: List<GogoSource>?,
//val track: List<Any?>,
//val advertising: List<Any?>,
//val linkiframe: String
)
data class GogoSource(
val file: String,
val label: String?,
val type: String?,
val default: String? = null
@JsonProperty("file") val file: String,
@JsonProperty("label") val label: String?,
@JsonProperty("type") val type: String?,
@JsonProperty("default") val default: String? = null
)
private suspend fun extractVideos(uri: String, callback: (ExtractorLink) -> Unit) {

View File

@ -172,6 +172,13 @@ class ZoroProvider : MainAPI() {
}
}
private fun Element?.getActor(): Actor? {
val image =
fixUrlNull(this?.selectFirst(".pi-avatar > img")?.attr("data-src")) ?: return null
val name = this?.selectFirst(".pi-detail > .pi-name")?.text() ?: return null
return Actor(name = name, image = image)
}
override suspend fun load(url: String): LoadResponse {
val html = app.get(url).text
val document = Jsoup.parse(html)
@ -222,6 +229,23 @@ class ZoroProvider : MainAPI() {
)
}
val actors = document.select("div.block-actors-content > div.bac-list-wrap > div.bac-item")
?.mapNotNull { head ->
val subItems = head.select(".per-info") ?: return@mapNotNull null
if(subItems.isEmpty()) return@mapNotNull null
var role: ActorRole? = null
val mainActor = subItems.first()?.let {
role = when (it.selectFirst(".pi-detail > .pi-cast")?.text()?.trim()) {
"Supporting" -> ActorRole.Supporting
"Main" -> ActorRole.Main
else -> null
}
it.getActor()
} ?: return@mapNotNull null
val voiceActor = if(subItems.size >= 2) subItems[1]?.getActor() else null
ActorData(actor = mainActor, role = role, voiceActor = voiceActor)
}
val recommendations =
document.select("#main-content > section > .tab-content > div > .film_list-wrap > .flw-item")
.mapNotNull { head ->
@ -254,6 +278,7 @@ class ZoroProvider : MainAPI() {
plot = description
this.tags = tags
this.recommendations = recommendations
this.actors = actors
}
}

View File

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.metaproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.setActors
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.uwetrottmann.tmdb2.Tmdb
import com.uwetrottmann.tmdb2.entities.*
@ -79,6 +80,15 @@ open class TmdbProvider : MainAPI() {
)
}
private fun List<CastMember?>?.toActors(): List<Pair<Actor, String?>>? {
return this?.mapNotNull {
Pair(
Actor(it?.name ?: return@mapNotNull null, getImageUrl(it.profile_path)),
it.character
)
}
}
private fun TvShow.toLoadResponse(): TvSeriesLoadResponse {
val episodes = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 }
?.mapNotNull { season ->
@ -112,57 +122,56 @@ open class TmdbProvider : MainAPI() {
}
}?.flatten() ?: listOf()
return TvSeriesLoadResponse(
return newTvSeriesLoadResponse(
this.name ?: this.original_name,
getUrl(id, true),
this@TmdbProvider.apiName,
TvType.TvSeries,
episodes,
getImageUrl(this.poster_path),
this.first_air_date?.let {
episodes
) {
posterUrl = getImageUrl(poster_path)
year = first_air_date?.let {
Calendar.getInstance().apply {
time = it
}.get(Calendar.YEAR)
},
this.overview,
null, // this.status
this.external_ids?.imdb_id,
this.rating,
this.genres?.mapNotNull { it.name },
this.episode_run_time?.average()?.toInt(),
null,
(this.recommendations ?: this.similar)?.results?.map { it.toSearchResponse() }
)
}
plot = overview
imdbId = external_ids?.imdb_id
tags = genres?.mapNotNull { it.name }
duration = episode_run_time?.average()?.toInt()
rating = this@toLoadResponse.rating
recommendations = (this@toLoadResponse.recommendations
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
setActors(credits?.cast?.toList().toActors())
}
}
private fun Movie.toLoadResponse(): MovieLoadResponse {
println("TRAILRES::::::: ${this.similar} :::: ${this.recommendations} ")
return MovieLoadResponse(
this.title ?: this.original_title,
getUrl(id, false),
this@TmdbProvider.apiName,
TvType.Movie,
TmdbLink(
return newMovieLoadResponse(
this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink(
this.imdb_id,
this.id,
null,
null,
this.title ?: this.original_title,
).toJson(),
getImageUrl(this.poster_path),
this.release_date?.let {
).toJson()
) {
posterUrl = getImageUrl(poster_path)
year = release_date?.let {
Calendar.getInstance().apply {
time = it
}.get(Calendar.YEAR)
},
this.overview,
null,//this.status
this.rating,
this.genres?.mapNotNull { it.name },
this.runtime,
null,
(this.recommendations ?: this.similar)?.results?.map { it.toSearchResponse() }
)
}
plot = overview
imdbId = external_ids?.imdb_id
tags = genres?.mapNotNull { it.name }
duration = runtime
rating = this@toLoadResponse.rating
recommendations = (this@toLoadResponse.recommendations
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
setActors(credits?.cast?.toList().toActors())
}
}
override suspend fun getMainPage(): HomePageResponse {
@ -248,23 +257,38 @@ open class TmdbProvider : MainAPI() {
return if (isTvSeries) {
val body = tmdb.tvService().tv(id, "en-US").awaitResponse().body()
val response = body?.toLoadResponse()
if (response != null && response.recommendations.isNullOrEmpty()) {
tmdb.tvService().recommendations(id, 1,"en-US").awaitResponse().body()?.let {
it.results?.map { res -> res.toSearchResponse() }
}?.let { list ->
response.recommendations = list
}
if (response != null) {
if (response.recommendations.isNullOrEmpty())
tmdb.tvService().recommendations(id, 1, "en-US").awaitResponse().body()
?.let {
it.results?.map { res -> res.toSearchResponse() }
}?.let { list ->
response.recommendations = list
}
if (response.actors.isNullOrEmpty())
tmdb.tvService().credits(id, "en-US").awaitResponse().body()?.let {
response.setActors(it.cast?.toActors())
}
}
response
} else {
val body = tmdb.moviesService().summary(id, "en-US").awaitResponse().body()
val response = body?.toLoadResponse()
if (response != null && response.recommendations.isNullOrEmpty()) {
tmdb.moviesService().recommendations(id, 1,"en-US").awaitResponse().body()?.let {
it.results?.map { res -> res.toSearchResponse() }
}?.let { list ->
response.recommendations = list
}
if (response != null) {
if (response.recommendations.isNullOrEmpty())
tmdb.moviesService().recommendations(id, 1, "en-US").awaitResponse().body()
?.let {
it.results?.map { res -> res.toSearchResponse() }
}?.let { list ->
response.recommendations = list
}
if (response.actors.isNullOrEmpty())
tmdb.moviesService().credits(id).awaitResponse().body()?.let {
response.setActors(it.cast?.toActors())
}
}
response
}
@ -296,4 +320,4 @@ open class TmdbProvider : MainAPI() {
it.movie?.toSearchResponse() ?: it.tvShow?.toSearchResponse()
}
}
}
}

View File

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.movieproviders
import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities

View File

@ -26,7 +26,10 @@ class MeloMovieProvider : MainAPI() {
//"mppa" for tags
)
data class MeloMovieLink(val name: String, val link: String)
data class MeloMovieLink(
@JsonProperty("name") val name: String,
@JsonProperty("link") val link: String
)
override suspend fun quickSearch(query: String): List<SearchResponse> {
return search(query)
@ -106,7 +109,16 @@ class MeloMovieProvider : MainAPI() {
): Boolean {
val links = parseJson<List<MeloMovieLink>>(data)
for (link in links) {
callback.invoke(ExtractorLink(this.name, link.name, link.link, "", getQualityFromName(link.name), false))
callback.invoke(
ExtractorLink(
this.name,
link.name,
link.link,
"",
getQualityFromName(link.name),
false
)
)
}
return true
}
@ -125,11 +137,13 @@ class MeloMovieProvider : MainAPI() {
val type = findUsingRegex("var posttype = ([0-9]*)")?.toInt() ?: return null
val titleInfo = document.selectFirst("div.movie_detail_title > div > div > h1")
val title = titleInfo.ownText()
val year = titleInfo.selectFirst("> a")?.text()?.replace("(", "")?.replace(")", "")?.toIntOrNull()
val year =
titleInfo.selectFirst("> a")?.text()?.replace("(", "")?.replace(")", "")?.toIntOrNull()
val plot = document.selectFirst("div.col-lg-12 > p").text()
if (type == 1) { // MOVIE
val serialize = document.selectFirst("table.accordion__list") ?: throw ErrorLoadingException("No links found")
val serialize = document.selectFirst("table.accordion__list")
?: throw ErrorLoadingException("No links found")
return MovieLoadResponse(
title,
url,
@ -143,15 +157,19 @@ class MeloMovieProvider : MainAPI() {
)
} else if (type == 2) {
val episodes = ArrayList<TvSeriesEpisode>()
val seasons = document.select("div.accordion__card") ?: throw ErrorLoadingException("No episodes found")
val seasons = document.select("div.accordion__card")
?: throw ErrorLoadingException("No episodes found")
for (s in seasons) {
val season =
s.selectFirst("> div.card-header > button > span").text().replace("Season: ", "").toIntOrNull()
s.selectFirst("> div.card-header > button > span").text()
.replace("Season: ", "").toIntOrNull()
val localEpisodes = s.select("> div.collapse > div > div > div.accordion__card")
for (e in localEpisodes) {
val episode =
e.selectFirst("> div.card-header > button > span").text().replace("Episode: ", "").toIntOrNull()
val links = e.selectFirst("> div.collapse > div > table.accordion__list") ?: continue
e.selectFirst("> div.card-header > button > span").text()
.replace("Episode: ", "").toIntOrNull()
val links =
e.selectFirst("> div.collapse > div > table.accordion__list") ?: continue
val data = serializeData(links)
episodes.add(TvSeriesEpisode(null, season, episode, data))
}

View File

@ -1,11 +1,12 @@
package com.lagradost.cloudstream3.movieproviders
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.*
import kotlin.collections.ArrayList
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.loadExtractor
class PelisflixProvider:MainAPI() {
class PelisflixProvider : MainAPI() {
override val mainUrl = "https://pelisflix.li"
override val name = "Pelisflix"
override val lang = "es"
@ -16,6 +17,7 @@ class PelisflixProvider:MainAPI() {
TvType.Movie,
TvType.TvSeries,
)
override suspend fun getMainPage(): HomePageResponse {
val items = ArrayList<HomePageList>()
val urls = listOf(
@ -41,12 +43,13 @@ class PelisflixProvider:MainAPI() {
items.add(HomePageList(i.second, home))
} catch (e: Exception) {
logError(e)
logError(e)
}
}
if (items.size <= 0) throw ErrorLoadingException()
return HomePageResponse(items)
}
override suspend fun search(query: String): List<SearchResponse> {
val url = "$mainUrl/?s=$query"
val doc = app.get(url).document
@ -93,17 +96,21 @@ class PelisflixProvider:MainAPI() {
.replace(descRegex2, "").replace(descRegex3, "")
.replace(descRegex4, "").replace(descRegex5, "")
val desc2Regex = Regex("(G(e|é)nero:.*..)")
val descipt2 = document.selectFirst("div.Description").text().replace(desc2Regex,"")
val descipt2 = document.selectFirst("div.Description").text().replace(desc2Regex, "")
val rating =
document.selectFirst("div.rating-content button.like-mov span.vot_cl")?.text()?.toFloatOrNull()
document.selectFirst("div.rating-content button.like-mov span.vot_cl")?.text()
?.toFloatOrNull()
?.times(0)?.toInt()
val year = document.selectFirst("span.Date")?.text()
val duration = if (type == TvType.Movie) document.selectFirst(".Container .Container span.Time").text() else null
val duration =
if (type == TvType.Movie) document.selectFirst(".Container .Container span.Time")
.text() else null
val postercss = document.selectFirst("head").toString()
val posterRegex = Regex("(\"og:image\" content=\"https:\\/\\/seriesflix.video\\/wp-content\\/uploads\\/(\\d+)\\/(\\d+)\\/?.*.jpg)")
val posterRegex =
Regex("(\"og:image\" content=\"https:\\/\\/seriesflix.video\\/wp-content\\/uploads\\/(\\d+)\\/(\\d+)\\/?.*.jpg)")
val poster = try {
posterRegex.findAll(postercss).map {
it.value.replace("\"og:image\" content=\"","")
it.value.replace("\"og:image\" content=\"", "")
}.toList().first()
} catch (e: Exception) {
document.select(".TPostBg").attr("src")
@ -122,8 +129,8 @@ class PelisflixProvider:MainAPI() {
val episodeList = ArrayList<TvSeriesEpisode>()
for (season in list) {
val seasonDocument = app.get(season.second).document
for ((seasonInt, seasonUrl) in list) {
val seasonDocument = app.get(seasonUrl).document
val episodes = seasonDocument.select("table > tbody > tr")
if (episodes.isNotEmpty()) {
episodes.forEach { episode ->
@ -136,7 +143,7 @@ class PelisflixProvider:MainAPI() {
episodeList.add(
TvSeriesEpisode(
name,
season.first,
seasonInt,
epNum,
href,
fixUrlNull(epthumb),
@ -160,7 +167,6 @@ class PelisflixProvider:MainAPI() {
rating
)
} else {
return newMovieLoadResponse(
title,
url,
@ -186,14 +192,17 @@ class PelisflixProvider:MainAPI() {
val movieID = it.attr("data-id")
val serverID = it.attr("data-key")
val type = if (data.contains("pelicula")) 1 else 2
val url = "$mainUrl/?trembed=$serverID&trid=$movieID&trtype=$type" //This is to get the POST key value
val url =
"$mainUrl/?trembed=$serverID&trid=$movieID&trtype=$type" //This is to get the POST key value
val doc1 = app.get(url).document
doc1.select("div.Video iframe").apmap {
val iframe = it.attr("src")
val postkey = iframe.replace("/stream/index.php?h=","") // this obtains
val postkey = iframe.replace("/stream/index.php?h=", "") // this obtains
// djNIdHNCR2lKTGpnc3YwK3pyRCs3L2xkQmljSUZ4ai9ibTcza0JRODNMcmFIZ0hPejdlYW0yanJIL2prQ1JCZA POST KEY
app.post("https://pelisflix.li/stream/r.php",
headers = mapOf("Host" to "pelisflix.li",
app.post(
"https://pelisflix.li/stream/r.php",
headers = mapOf(
"Host" to "pelisflix.li",
"User-Agent" to USER_AGENT,
"Accept" to "ext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language" to "en-US,en;q=0.5",
@ -208,12 +217,13 @@ class PelisflixProvider:MainAPI() {
"Sec-Fetch-User" to "?1",
"Pragma" to "no-cache",
"Cache-Control" to "no-cache",
"TE" to "trailers"),
"TE" to "trailers"
),
params = mapOf(Pair("h", postkey)),
data = mapOf(Pair("h", postkey)),
data = mapOf(Pair("h", postkey)),
allowRedirects = false
).response.headers.values("location").apmap { link ->
val url1 = link.replace("#bu","")
val url1 = link.replace("#bu", "")
loadExtractor(url1, data, callback)
}
}

View File

@ -1,11 +1,12 @@
package com.lagradost.cloudstream3.movieproviders
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.*
import kotlin.collections.ArrayList
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.loadExtractor
class SeriesflixProvider:MainAPI() {
class SeriesflixProvider : MainAPI() {
override val mainUrl = "https://seriesflix.video"
override val name = "Seriesflix"
override val lang = "es"
@ -16,6 +17,7 @@ class SeriesflixProvider:MainAPI() {
TvType.Movie,
TvType.TvSeries,
)
override suspend fun getMainPage(): HomePageResponse {
val items = ArrayList<HomePageList>()
val urls = listOf(
@ -48,6 +50,7 @@ class SeriesflixProvider:MainAPI() {
if (items.size <= 0) throw ErrorLoadingException()
return HomePageResponse(items)
}
override suspend fun search(query: String): List<SearchResponse> {
val url = "$mainUrl/?s=$query"
val doc = app.get(url).document
@ -80,15 +83,14 @@ class SeriesflixProvider:MainAPI() {
}
override suspend fun load(url: String): LoadResponse? {
override suspend fun load(url: String): LoadResponse {
val type = if (url.contains("/movies/")) TvType.Movie else TvType.TvSeries
val document = app.get(url).document
val title = document.selectFirst("h1.Title").text()
val descRegex = Regex("(Recuerda.*Seriesflix.)")
val descipt = document.selectFirst("div.Description > p").text().replace(descRegex,"")
val descipt = document.selectFirst("div.Description > p").text().replace(descRegex, "")
val rating =
document.selectFirst("div.Vote > div.post-ratings > span")?.text()?.toFloatOrNull()
?.times(1000)?.toInt()
@ -100,10 +102,11 @@ class SeriesflixProvider:MainAPI() {
null
}
val postercss = document.selectFirst("head").toString()
val posterRegex = Regex("(\"og:image\" content=\"https:\\/\\/seriesflix.video\\/wp-content\\/uploads\\/(\\d+)\\/(\\d+)\\/?.*.jpg)")
val posterRegex =
Regex("(\"og:image\" content=\"https://seriesflix.video/wp-content/uploads/(\\d+)/(\\d+)/?.*.jpg)")
val poster = try {
posterRegex.findAll(postercss).map {
it.value.replace("\"og:image\" content=\"","")
it.value.replace("\"og:image\" content=\"", "")
}.toList().first()
} catch (e: Exception) {
document.select(".TPostBg").attr("src")
@ -186,14 +189,18 @@ class SeriesflixProvider:MainAPI() {
val movieID = it.attr("data-id")
val serverID = it.attr("data-key")
val type = if (data.contains("movies")) 1 else 2
val url = "$mainUrl/?trembed=$serverID&trid=$movieID&trtype=$type" //This is to get the POST key value
val url =
"$mainUrl/?trembed=$serverID&trid=$movieID&trtype=$type" //This is to get the POST key value
val doc1 = app.get(url).document
doc1.select("div.Video iframe").apmap {
val iframe = it.attr("src")
val postkey = iframe.replace("https://sc.seriesflix.video/index.php?h=","") // this obtains
val postkey =
iframe.replace("https://sc.seriesflix.video/index.php?h=", "") // this obtains
// djNIdHNCR2lKTGpnc3YwK3pyRCs3L2xkQmljSUZ4ai9ibTcza0JRODNMcmFIZ0hPejdlYW0yanJIL2prQ1JCZA POST KEY
app.post("https://sc.seriesflix.video/r.php",
headers = mapOf("Host" to "sc.seriesflix.video",
app.post(
"https://sc.seriesflix.video/r.php",
headers = mapOf(
"Host" to "sc.seriesflix.video",
"User-Agent" to USER_AGENT,
"Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language" to "en-US,en;q=0.5",
@ -206,12 +213,13 @@ class SeriesflixProvider:MainAPI() {
"Sec-Fetch-Dest" to "iframe",
"Sec-Fetch-Mode" to "navigate",
"Sec-Fetch-Site" to "same-origin",
"Sec-Fetch-User" to "?1",),
"Sec-Fetch-User" to "?1",
),
params = mapOf(Pair("h", postkey)),
data = mapOf(Pair("h", postkey)),
data = mapOf(Pair("h", postkey)),
allowRedirects = false
).response.headers.values("location").apmap {link ->
val url1 = link.replace("#bu","")
).response.headers.values("location").apmap { link ->
val url1 = link.replace("#bu", "")
loadExtractor(url1, data, callback)
}
}

View File

@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.movieproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.setActorNames
import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration
import com.lagradost.cloudstream3.network.WebViewResolver
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.toJson
@ -100,13 +102,37 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() {
val img = details.select("img.film-poster-img")
val posterUrl = img.attr("src")
val title = img.attr("title")
/*
val year = Regex("""[Rr]eleased:\s*(\d{4})""").find(
document.select("div.elements").text()
)?.groupValues?.get(1)?.toIntOrNull()
val duration = Regex("""[Dd]uration:\s*(\d*)""").find(
document.select("div.elements").text()
)?.groupValues?.get(1)?.trim()?.plus(" min")
)?.groupValues?.get(1)?.trim()?.plus(" min")*/
var duration = document.selectFirst(".fs-item > .duration").text()?.trim()
var year: Int? = null
var tags: List<String>? = null
var cast: List<String>? = null
document.select("div.elements > .row > div > .row-line")?.forEach { element ->
val type = element?.select(".type")?.text() ?: return@forEach
when {
type.contains("Released") -> {
year = Regex("\\d+").find(
element.ownText() ?: return@forEach
)?.groupValues?.firstOrNull()?.toIntOrNull()
}
type.contains("Genre") -> {
tags = element.select("a")?.mapNotNull { it.text() }
}
type.contains("Cast") -> {
cast = element.select("a")?.mapNotNull { it.text() }
}
type.contains("Duration") -> {
duration = duration ?: element.ownText()?.trim()
}
}
}
val plot = details.select("div.description").text().replace("Overview:", "").trim()
val isMovie = url.contains("/movie/")
@ -156,6 +182,8 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() {
this.posterUrl = posterUrl
this.plot = plot
setDuration(duration)
setActorNames(cast)
this.tags = tags
this.recommendations = recommendations
}
} else {
@ -201,6 +229,8 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() {
this.year = year
this.plot = plot
setDuration(duration)
setActorNames(cast)
this.tags = tags
this.recommendations = recommendations
}
}

View File

@ -779,10 +779,10 @@ class HomeFragment : Fragment() {
home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { view, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY
if (dy > 0) { //check for scroll down
home_api_fab?.hide()
home_api_fab?.shrink() // hide
} else if (dy < -5) {
if (view?.context?.isTvSettings() == false) {
home_api_fab?.show()
home_api_fab?.extend() // show
}
}
})

View File

@ -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]
}

View File

@ -391,6 +391,10 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
//requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR
downloadButton?.dispose()
updateUIListener = null
result_cast_items?.let {
PanelsChildGestureRegionObserver.Provider.get().unregister(it)
}
super.onDestroy()
}
@ -482,6 +486,22 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
setFormatText(result_meta_duration, R.string.duration_format, duration)
}
private fun setShow(showStatus : ShowStatus?) {
val status = when (showStatus) {
null -> null
ShowStatus.Ongoing -> R.string.status_ongoing
ShowStatus.Completed -> R.string.status_completed
}
if (status == null) {
result_meta_status?.isVisible = false
} else {
context?.getString(status)?.let {
result_meta_status?.text = it
}
}
}
private fun setYear(year: Int?) {
setFormatText(result_meta_year, R.string.year_format, year)
}
@ -490,6 +510,27 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f))
}
private fun setActors(actors: List<ActorData>?) {
if (actors.isNullOrEmpty()) {
result_cast_text?.isVisible = false
result_cast_items?.isVisible = false
} else {
val isImage = actors.first().actor.image != null
if (isImage) {
(result_cast_items?.adapter as ActorAdaptor?)?.apply {
updateList(actors)
}
result_cast_text?.isVisible = false
result_cast_items?.isVisible = true
} else {
result_cast_text?.isVisible = true
result_cast_items?.isVisible = false
setFormatText(result_cast_text, R.string.cast_format,
actors.joinToString { it.actor.name })
}
}
}
private fun setRecommendations(rec: List<SearchResponse>?) {
val isInvalid = rec.isNullOrEmpty()
result_recommendations?.isGone = isInvalid
@ -540,6 +581,10 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
result_cast_items?.let {
PanelsChildGestureRegionObserver.Provider.get().register(it)
}
result_cast_items?.adapter = ActorAdaptor(mutableListOf())
fixGrid()
result_recommendations?.spanCount = 3
result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
@ -1281,22 +1326,17 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
}
}
val metadataInfoArray = ArrayList<Pair<Int, String>>()
if (d is AnimeLoadResponse) {
val status = when (d.showStatus) {
null -> null
ShowStatus.Ongoing -> R.string.status_ongoing
ShowStatus.Completed -> R.string.status_completed
}
if (status != null) {
metadataInfoArray.add(Pair(R.string.status, getString(status)))
}
val showStatus = when (d) {
is TvSeriesLoadResponse -> d.showStatus
is AnimeLoadResponse -> d.showStatus
else -> null
}
setShow(showStatus)
setDuration(d.duration)
setYear(d.year)
setRating(d.rating)
setRecommendations(d.recommendations)
setActors(d.actors)
result_meta_site?.text = d.apiName
@ -1509,7 +1549,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
val showFillers =
settingsManager.getBoolean(ctx.getString(R.string.show_fillers_key), true)
settingsManager.getBoolean(ctx.getString(R.string.show_fillers_key), false)
val tempUrl = url
if (tempUrl != null) {
@ -1537,6 +1577,13 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
}
}
}
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
}
override fun onPause() {
super.onPause()
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
}
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {

View File

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.utils
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
@ -17,7 +18,10 @@ const val RESULT_SEASON = "result_season"
const val RESULT_DUB = "result_dub"
object DataStoreHelper {
data class PosDur(val position: Long, val duration: Long)
data class PosDur(
@JsonProperty("position") val position: Long,
@JsonProperty("duration") val duration: Long
)
fun PosDur.fixVisual(): PosDur {
if (duration <= 0) return PosDur(0, duration)
@ -29,31 +33,31 @@ object DataStoreHelper {
}
data class BookmarkedData(
override val id: Int?,
val bookmarkedTime: Long,
val latestUpdatedTime: Long,
override val name: String,
override val url: String,
override val apiName: String,
override val type: TvType,
override val posterUrl: String?,
val year: Int?,
@JsonProperty("id") override val id: Int?,
@JsonProperty("bookmarkedTime") val bookmarkedTime: Long,
@JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long,
@JsonProperty("name") override val name: String,
@JsonProperty("url") override val url: String,
@JsonProperty("apiName") override val apiName: String,
@JsonProperty("type") override val type: TvType,
@JsonProperty("posterUrl") override val posterUrl: String?,
@JsonProperty("year") val year: Int?,
) : SearchResponse
data class ResumeWatchingResult(
override val name: String,
override val url: String,
override val apiName: String,
override val type: TvType,
override val posterUrl: String?,
@JsonProperty("name") override val name: String,
@JsonProperty("url") override val url: String,
@JsonProperty("apiName") override val apiName: String,
@JsonProperty("type") override val type: TvType,
@JsonProperty("posterUrl") override val posterUrl: String?,
val watchPos: PosDur?,
@JsonProperty("watchPos") val watchPos: PosDur?,
override val id: Int?,
val parentId: Int?,
val episode: Int?,
val season: Int?,
val isFromDownload: Boolean,
@JsonProperty("id") override val id: Int?,
@JsonProperty("parentId") val parentId: Int?,
@JsonProperty("episode") val episode: Int?,
@JsonProperty("season") val season: Int?,
@JsonProperty("isFromDownload") val isFromDownload: Boolean,
) : SearchResponse
var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
@ -124,7 +128,7 @@ object DataStoreHelper {
}
fun getViewPos(id: Int?): PosDur? {
if(id == null) return null
if (id == null) return null
return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null)
}
@ -148,7 +152,13 @@ object DataStoreHelper {
}
fun getResultWatchState(id: Int): WatchType {
return WatchType.fromInternalId(getKey<Int>("$currentAccount/$RESULT_WATCH_STATE", id.toString(), null))
return WatchType.fromInternalId(
getKey<Int>(
"$currentAccount/$RESULT_WATCH_STATE",
id.toString(),
null
)
)
}
fun getResultSeason(id: Int): Int {

View File

@ -1,37 +1,38 @@
package com.lagradost.cloudstream3.utils
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
object VideoDownloadHelper {
data class DownloadEpisodeCached(
val name: String?,
val poster: String?,
val episode: Int,
val season: Int?,
override val id: Int,
val parentId: Int,
val rating: Int?,
val description: String?,
val cacheTime: Long,
@JsonProperty("name") val name: String?,
@JsonProperty("poster") val poster: String?,
@JsonProperty("episode") val episode: Int,
@JsonProperty("season") val season: Int?,
@JsonProperty("id") override val id: Int,
@JsonProperty("parentId") val parentId: Int,
@JsonProperty("rating") val rating: Int?,
@JsonProperty("description") val description: String?,
@JsonProperty("cacheTime") val cacheTime: Long,
) : EasyDownloadButton.IMinimumData
data class DownloadHeaderCached(
val apiName: String,
val url: String,
val type: TvType,
val name: String,
val poster: String?,
val id: Int,
val cacheTime: Long,
@JsonProperty("apiName") val apiName: String,
@JsonProperty("url") val url: String,
@JsonProperty("type") val type: TvType,
@JsonProperty("name") val name: String,
@JsonProperty("poster") val poster: String?,
@JsonProperty("id") val id: Int,
@JsonProperty("cacheTime") val cacheTime: Long,
)
data class ResumeWatching(
val parentId: Int,
val episodeId: Int,
val episode: Int?,
val season: Int?,
val updateTime : Long,
val isFromDownload: Boolean,
@JsonProperty("parentId") val parentId: Int,
@JsonProperty("episodeId") val episodeId: Int,
@JsonProperty("episode") val episode: Int?,
@JsonProperty("season") val season: Int?,
@JsonProperty("updateTime") val updateTime: Long,
@JsonProperty("isFromDownload") val isFromDownload: Boolean,
)
}

View File

@ -108,44 +108,44 @@ object VideoDownloadManager {
}
data class DownloadEpisodeMetadata(
val id: Int,
val mainName: String,
val sourceApiName: String?,
val poster: String?,
val name: String?,
val season: Int?,
val episode: Int?
@JsonProperty("id") val id: Int,
@JsonProperty("mainName") val mainName: String,
@JsonProperty("sourceApiName") val sourceApiName: String?,
@JsonProperty("poster") val poster: String?,
@JsonProperty("name") val name: String?,
@JsonProperty("season") val season: Int?,
@JsonProperty("episode") val episode: Int?
)
data class DownloadItem(
val source: String?,
val folder: String?,
val ep: DownloadEpisodeMetadata,
val links: List<ExtractorLink>,
@JsonProperty("source") val source: String?,
@JsonProperty("folder") val folder: String?,
@JsonProperty("ep") val ep: DownloadEpisodeMetadata,
@JsonProperty("links") val links: List<ExtractorLink>,
)
data class DownloadResumePackage(
val item: DownloadItem,
val linkIndex: Int?,
@JsonProperty("item") val item: DownloadItem,
@JsonProperty("linkIndex") val linkIndex: Int?,
)
data class DownloadedFileInfo(
val totalBytes: Long,
val relativePath: String,
val displayName: String,
val extraInfo: String? = null,
val basePath: String? = null // null is for legacy downloads. See getDefaultPath()
@JsonProperty("totalBytes") val totalBytes: Long,
@JsonProperty("relativePath") val relativePath: String,
@JsonProperty("displayName") val displayName: String,
@JsonProperty("extraInfo") val extraInfo: String? = null,
@JsonProperty("basePath") val basePath: String? = null // null is for legacy downloads. See getDefaultPath()
)
data class DownloadedFileInfoResult(
val fileLength: Long,
val totalBytes: Long,
val path: Uri,
@JsonProperty("fileLength") val fileLength: Long,
@JsonProperty("totalBytes") val totalBytes: Long,
@JsonProperty("path") val path: Uri,
)
data class DownloadQueueResumePackage(
val index: Int,
val pkg: DownloadResumePackage,
@JsonProperty("index") val index: Int,
@JsonProperty("pkg") val pkg: DownloadResumePackage,
)
private const val SUCCESS_DOWNLOAD_DONE = 1

View File

@ -8,10 +8,19 @@ import com.lagradost.cloudstream3.R
import kotlin.math.max
class FlowLayout : ViewGroup {
var itemSpacing : Int = 0
constructor(context: Context?) : super(context)
@JvmOverloads
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr)
//@JvmOverloads
//constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr)
@SuppressLint("CustomViewStyleable")
internal constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) {
val t = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout)
itemSpacing = t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_itemSpacing, 0);
t.recycle()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val realWidth = MeasureSpec.getSize(widthMeasureSpec)
@ -29,13 +38,13 @@ class FlowLayout : ViewGroup {
//check if child can be placed in the current row, else go to next line
if (currentChildHookPointx + childWidth > realWidth) {
//new line
currentWidth = Math.max(currentWidth, currentChildHookPointx)
currentWidth = max(currentWidth, currentChildHookPointx)
//reset for new line
currentChildHookPointx = 0
currentChildHookPointy += childHeight
}
val nextChildHookPointx = currentChildHookPointx + childWidth
val nextChildHookPointx = currentChildHookPointx + childWidth + if(childWidth == 0) 0 else itemSpacing
val nextChildHookPointy = currentChildHookPointy
currentHeight = max(currentHeight, currentChildHookPointy + childHeight)
val lp = child.layoutParams as LayoutParams
@ -44,7 +53,7 @@ class FlowLayout : ViewGroup {
currentChildHookPointx = nextChildHookPointx
currentChildHookPointy = nextChildHookPointy
}
currentWidth = Math.max(currentChildHookPointx, currentWidth)
currentWidth = max(currentChildHookPointx, currentWidth)
setMeasuredDimension(resolveSize(currentWidth, widthMeasureSpec),
resolveSize(currentHeight, heightMeasureSpec))
}
@ -83,7 +92,7 @@ class FlowLayout : ViewGroup {
@SuppressLint("CustomViewStyleable")
internal constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) {
val t = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout)
spacing = 0 //t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_layout_space, 0);
spacing = 0//t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_itemSpacing, 0);
t.recycle()
}

View 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>

View File

@ -506,8 +506,10 @@
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:visibility="gone"
tools:visibility="visible"
android:text="@string/home_source"
android:id="@+id/home_api_fab"
app:icon="@drawable/ic_baseline_filter_list_24"
style="@style/ExtendedFloatingActionButton"
android:textColor="?attr/textColor"
tools:ignore="ContentDescription" />
</FrameLayout>

View File

@ -311,6 +311,7 @@
android:layout_height="wrap_content" />
<com.lagradost.cloudstream3.widget.FlowLayout
app:itemSpacing="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -322,56 +323,28 @@
<TextView
android:id="@+id/result_meta_type"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:minHeight="24dp"
android:gravity="center"
tools:text="Movie"
android:layout_gravity="center_vertical"
android:textColor="?attr/textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
style="@style/ResultInfoText"
tools:text="Movie" />
<TextView
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:minHeight="24dp"
android:gravity="center"
tools:text="2022"
android:id="@+id/result_meta_year"
android:layout_marginStart="10dp"
tools:text="2021"
android:layout_gravity="center_vertical"
android:textColor="?attr/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
style="@style/ResultInfoText" />
<TextView
android:id="@+id/result_meta_rating"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:minHeight="24dp"
android:gravity="center"
tools:text="Rated: 8.5/10.0"
android:layout_gravity="center_vertical"
android:textColor="?attr/textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
style="@style/ResultInfoText"
tools:text="Rated: 8.5/10.0" />
<TextView
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:minHeight="24dp"
android:gravity="center"
android:id="@+id/result_meta_duration"
android:id="@+id/result_meta_status"
style="@style/ResultInfoText"
tools:text="Ongoing" />
tools:text="121min"
android:layout_gravity="center_vertical"
android:textColor="?attr/textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
style="@style/ResultInfoText"
android:id="@+id/result_meta_duration"
tools:text="121min" />
</com.lagradost.cloudstream3.widget.FlowLayout>
<FrameLayout
@ -428,6 +401,28 @@
<requestFocus />
</com.google.android.material.button.MaterialButton>
<TextView
android:maxLines="2"
android:ellipsize="end"
android:layout_marginBottom="5dp"
android:textColor="?attr/grayTextColor"
android:id="@+id/result_cast_text"
android:textSize="15sp"
tools:text="Cast: Joe Ligma"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
tools:itemCount="2"
android:fadingEdge="horizontal"
android:requiresFadingEdge="horizontal"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:id="@+id/result_cast_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/cast_item" />
<TextView
android:textColor="?attr/grayTextColor"
android:id="@+id/result_vpn"
@ -564,6 +559,7 @@
android:text="@string/resume"
app:icon="@drawable/ic_baseline_play_arrow_24"
android:layout_width="match_parent" />
<com.google.android.material.button.MaterialButton
android:layout_marginBottom="10dp"
android:nextFocusUp="@id/result_bookmark_button"
@ -607,6 +603,7 @@
android:layout_weight="1"
android:visibility="visible"
tools:visibility="visible" />
<TextView
android:id="@+id/result_resume_series_progress_text"
android:layout_gravity="center"

View File

@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FlowLayout_Layout"/>
<declare-styleable name="FlowLayout_Layout_layout_space"/>
<declare-styleable name="FlowLayout_Layout">
<attr format="dimension" name="itemSpacing" />
</declare-styleable>
<declare-styleable name="FlowLayout_Layout_layout_space" />
<declare-styleable name="CustomCast">
<attr name="customCastBackgroundColor" format="color"/>
<attr name="customCastBackgroundColor" format="color" />
</declare-styleable>
<style name="customCastDefColor">
@ -12,19 +14,19 @@
</style>
<declare-styleable name="MainColors">
<attr name="colorPrimary" format="color"/>
<attr name="colorSearch" format="color"/>
<attr name="colorOngoing" format="color"/>
<attr name="colorPrimaryDark" format="color"/>
<attr name="colorPrimary" format="color" />
<attr name="colorSearch" format="color" />
<attr name="colorOngoing" format="color" />
<attr name="colorPrimaryDark" format="color" />
<attr name="primaryGrayBackground" format="color"/>
<attr name="primaryBlackBackground" format="color"/>
<attr name="iconGrayBackground" format="color"/>
<attr name="boxItemBackground" format="color"/>
<attr name="primaryGrayBackground" format="color" />
<attr name="primaryBlackBackground" format="color" />
<attr name="iconGrayBackground" format="color" />
<attr name="boxItemBackground" format="color" />
<attr name="textColor" format="color"/>
<attr name="grayTextColor" format="color"/>
<attr name="iconColor" format="color"/>
<attr name="white" format="color"/>
<attr name="textColor" format="color" />
<attr name="grayTextColor" format="color" />
<attr name="iconColor" format="color" />
<attr name="white" format="color" />
</declare-styleable>
</resources>

View File

@ -46,6 +46,7 @@
<string name="rating_format" translatable="false" formatted="true">%.1f/10.0</string>
<string name="year_format" translatable="false" formatted="true">%d</string>
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
<string name="cast_format" formatted="true">Cast: %s</string>
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
<string name="result_poster_img_des">Poster</string>
@ -384,4 +385,7 @@
<string name="player_loaded_subtitles" formatted="true">Loaded %s</string>
<string name="player_load_subtitles">Load from file</string>
<string name="downloaded_file">Downloaded file</string>
<string name="actor_main">Main</string>
<string name="actor_supporting">Supporting</string>
<string name="home_source">Source</string>
</resources>

View File

@ -229,6 +229,19 @@
<item name="android:fontFamily">@font/google_sans</item>
</style>
<style name="ResultInfoText">
<item name="android:layout_gravity">center_vertical</item>
<item name="textColor">?attr/white</item>
<item name="android:gravity">center</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:minHeight">24dp</item>
<item name="android:minWidth">0dp</item>
<!-- <item name="android:paddingStart">5dp</item>
<item name="android:paddingEnd">5dp</item>-->
</style>
<style name="AppMaterialButtonStyle" parent="Widget.MaterialComponents.Button">
<item name="android:fontFamily">@font/google_sans</item>
</style>

View File

@ -95,7 +95,7 @@
android:key="@string/show_fillers_key"
android:icon="@drawable/ic_baseline_skip_next_24"
android:title="@string/show_fillers_settings"
android:defaultValue="true" />
android:defaultValue="false" />
<Preference
android:key="@string/dns_key"
android:title="@string/dns_pref"