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

View file

@ -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()
} }

View file

@ -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) {

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 { 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
} }
} }

View file

@ -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())
it.results?.map { res -> res.toSearchResponse() } tmdb.tvService().recommendations(id, 1, "en-US").awaitResponse().body()
}?.let { list -> ?.let {
response.recommendations = list 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 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())
it.results?.map { res -> res.toSearchResponse() } tmdb.moviesService().recommendations(id, 1, "en-US").awaitResponse().body()
}?.let { list -> ?.let {
response.recommendations = list 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 response
} }
@ -296,4 +320,4 @@ open class TmdbProvider : MainAPI() {
it.movie?.toSearchResponse() ?: it.tvShow?.toSearchResponse() 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.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

View file

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

View file

@ -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(
@ -41,12 +43,13 @@ class PelisflixProvider:MainAPI() {
items.add(HomePageList(i.second, home)) items.add(HomePageList(i.second, home))
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} }
} }
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)
} }
} }

View file

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

View file

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

View file

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

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 //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) {
metadataInfoArray.add(Pair(R.string.status, getString(status)))
}
} }
setShow(showStatus)
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>) {

View file

@ -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 {

View file

@ -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,
) )
} }

View file

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

View file

@ -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()
} }

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

View file

@ -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"

View file

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

View file

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

View file

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

View file

@ -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"