mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
cast + minify (json fix) + ongoing + UI change
This commit is contained in:
parent
bd14fad607
commit
91be244d61
24 changed files with 731 additions and 270 deletions
|
@ -36,7 +36,7 @@ android {
|
|||
targetSdkVersion 30
|
||||
|
||||
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 {
|
||||
|
|
|
@ -194,7 +194,8 @@ object APIHolder {
|
|||
val currentPrefMedia =
|
||||
settingsManager.getInt(this.getString(R.string.prefer_media_type_key), 0)
|
||||
val langs = this.getApiProviderLangSettings()
|
||||
val allApis = apis.filter { langs.contains(it.lang) }.filter { api -> api.hasMainPage || !hasHomePageIsRequired}
|
||||
val allApis = apis.filter { langs.contains(it.lang) }
|
||||
.filter { api -> api.hasMainPage || !hasHomePageIsRequired }
|
||||
return if (currentPrefMedia < 1) {
|
||||
allApis
|
||||
} else {
|
||||
|
@ -426,6 +427,23 @@ interface SearchResponse {
|
|||
val id: Int?
|
||||
}
|
||||
|
||||
enum class ActorRole {
|
||||
Main,
|
||||
Supporting,
|
||||
}
|
||||
|
||||
data class Actor(
|
||||
val name: String,
|
||||
val image: String? = null,
|
||||
)
|
||||
|
||||
data class ActorData(
|
||||
val actor: Actor,
|
||||
val role: ActorRole? = null,
|
||||
val roleString : String? = null,
|
||||
val voiceActor: Actor? = null,
|
||||
)
|
||||
|
||||
data class AnimeSearchResponse(
|
||||
override val name: String,
|
||||
override val url: String,
|
||||
|
@ -488,6 +506,38 @@ interface LoadResponse {
|
|||
var duration: Int? // in minutes
|
||||
val trailerUrl: String?
|
||||
val recommendations: List<SearchResponse>?
|
||||
var actors: List<ActorData>?
|
||||
|
||||
companion object {
|
||||
fun LoadResponse.setActorNames(actors: List<String>?) {
|
||||
this.actors = actors?.map { ActorData(Actor(it)) }
|
||||
}
|
||||
|
||||
fun LoadResponse.setActors(actors: List<Pair<Actor, String?>>?) {
|
||||
println("ACTORS: ${actors?.size}")
|
||||
this.actors = actors?.map { (actor, role) -> ActorData(actor, roleString = role) }
|
||||
}
|
||||
|
||||
fun LoadResponse.setDuration(input: String?) {
|
||||
val cleanInput = input?.trim()?.replace(" ","") ?: return
|
||||
Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
|
||||
if (values.size == 3) {
|
||||
val hours = values[1].toIntOrNull()
|
||||
val minutes = values[2].toIntOrNull()
|
||||
this.duration = if (minutes != null && hours != null) {
|
||||
hours * 60 + minutes
|
||||
} else null
|
||||
if (this.duration != null) return
|
||||
}
|
||||
}
|
||||
Regex("([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
|
||||
if (values.size == 2) {
|
||||
this.duration = values[1].toIntOrNull()
|
||||
if (this.duration != null) return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun LoadResponse?.isEpisodeBased(): Boolean {
|
||||
|
@ -530,6 +580,7 @@ data class TorrentLoadResponse(
|
|||
override var duration: Int? = null,
|
||||
override var trailerUrl: String? = null,
|
||||
override var recommendations: List<SearchResponse>? = null,
|
||||
override var actors: List<ActorData>? = null,
|
||||
) : LoadResponse
|
||||
|
||||
data class AnimeLoadResponse(
|
||||
|
@ -556,6 +607,7 @@ data class AnimeLoadResponse(
|
|||
override var duration: Int? = null,
|
||||
override var trailerUrl: String? = null,
|
||||
override var recommendations: List<SearchResponse>? = null,
|
||||
override var actors: List<ActorData>? = null,
|
||||
) : LoadResponse
|
||||
|
||||
fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List<AnimeEpisode>?) {
|
||||
|
@ -591,6 +643,7 @@ data class MovieLoadResponse(
|
|||
override var duration: Int? = null,
|
||||
override var trailerUrl: String? = null,
|
||||
override var recommendations: List<SearchResponse>? = null,
|
||||
override var actors: List<ActorData>? = null,
|
||||
) : LoadResponse
|
||||
|
||||
fun MainAPI.newMovieLoadResponse(
|
||||
|
@ -611,24 +664,6 @@ fun MainAPI.newMovieLoadResponse(
|
|||
return builder
|
||||
}
|
||||
|
||||
fun LoadResponse.setDuration(input: String?) {
|
||||
if (input == null) return
|
||||
Regex("([0-9]*)h.*?([0-9]*)m").matchEntire(input)?.groupValues?.let { values ->
|
||||
if (values.size == 3) {
|
||||
val hours = values[1].toIntOrNull()
|
||||
val minutes = values[2].toIntOrNull()
|
||||
this.duration = if (minutes != null && hours != null) {
|
||||
hours * 60 + minutes
|
||||
} else null
|
||||
}
|
||||
}
|
||||
Regex("([0-9]*)m").matchEntire(input)?.groupValues?.let { values ->
|
||||
if (values.size == 2) {
|
||||
this.duration = values[1].toIntOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TvSeriesEpisode(
|
||||
val name: String? = null,
|
||||
val season: Int? = null,
|
||||
|
@ -658,6 +693,7 @@ data class TvSeriesLoadResponse(
|
|||
override var duration: Int? = null,
|
||||
override var trailerUrl: String? = null,
|
||||
override var recommendations: List<SearchResponse>? = null,
|
||||
override var actors: List<ActorData>? = null,
|
||||
) : LoadResponse
|
||||
|
||||
fun MainAPI.newTvSeriesLoadResponse(
|
||||
|
@ -682,6 +718,7 @@ fun fetchUrls(text: String?): List<String> {
|
|||
if (text.isNullOrEmpty()) {
|
||||
return listOf()
|
||||
}
|
||||
val linkRegex = Regex("""(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*))""")
|
||||
val linkRegex =
|
||||
Regex("""(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*))""")
|
||||
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
|
@ -218,18 +219,18 @@ class GogoanimeProvider : MainAPI() {
|
|||
}
|
||||
|
||||
data class GogoSources(
|
||||
val source: List<GogoSource>?,
|
||||
val sourceBk: List<GogoSource>?,
|
||||
@JsonProperty("source") val source: List<GogoSource>?,
|
||||
@JsonProperty("sourceBk") val sourceBk: List<GogoSource>?,
|
||||
//val track: List<Any?>,
|
||||
//val advertising: List<Any?>,
|
||||
//val linkiframe: String
|
||||
)
|
||||
|
||||
data class GogoSource(
|
||||
val file: String,
|
||||
val label: String?,
|
||||
val type: String?,
|
||||
val default: String? = null
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("label") val label: String?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("default") val default: String? = null
|
||||
)
|
||||
|
||||
private suspend fun extractVideos(uri: String, callback: (ExtractorLink) -> Unit) {
|
||||
|
|
|
@ -172,6 +172,13 @@ class ZoroProvider : MainAPI() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Element?.getActor(): Actor? {
|
||||
val image =
|
||||
fixUrlNull(this?.selectFirst(".pi-avatar > img")?.attr("data-src")) ?: return null
|
||||
val name = this?.selectFirst(".pi-detail > .pi-name")?.text() ?: return null
|
||||
return Actor(name = name, image = image)
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val html = app.get(url).text
|
||||
val document = Jsoup.parse(html)
|
||||
|
@ -222,6 +229,23 @@ class ZoroProvider : MainAPI() {
|
|||
)
|
||||
}
|
||||
|
||||
val actors = document.select("div.block-actors-content > div.bac-list-wrap > div.bac-item")
|
||||
?.mapNotNull { head ->
|
||||
val subItems = head.select(".per-info") ?: return@mapNotNull null
|
||||
if(subItems.isEmpty()) return@mapNotNull null
|
||||
var role: ActorRole? = null
|
||||
val mainActor = subItems.first()?.let {
|
||||
role = when (it.selectFirst(".pi-detail > .pi-cast")?.text()?.trim()) {
|
||||
"Supporting" -> ActorRole.Supporting
|
||||
"Main" -> ActorRole.Main
|
||||
else -> null
|
||||
}
|
||||
it.getActor()
|
||||
} ?: return@mapNotNull null
|
||||
val voiceActor = if(subItems.size >= 2) subItems[1]?.getActor() else null
|
||||
ActorData(actor = mainActor, role = role, voiceActor = voiceActor)
|
||||
}
|
||||
|
||||
val recommendations =
|
||||
document.select("#main-content > section > .tab-content > div > .film_list-wrap > .flw-item")
|
||||
.mapNotNull { head ->
|
||||
|
@ -254,6 +278,7 @@ class ZoroProvider : MainAPI() {
|
|||
plot = description
|
||||
this.tags = tags
|
||||
this.recommendations = recommendations
|
||||
this.actors = actors
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.metaproviders
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.setActors
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import com.uwetrottmann.tmdb2.Tmdb
|
||||
import com.uwetrottmann.tmdb2.entities.*
|
||||
|
@ -79,6 +80,15 @@ open class TmdbProvider : MainAPI() {
|
|||
)
|
||||
}
|
||||
|
||||
private fun List<CastMember?>?.toActors(): List<Pair<Actor, String?>>? {
|
||||
return this?.mapNotNull {
|
||||
Pair(
|
||||
Actor(it?.name ?: return@mapNotNull null, getImageUrl(it.profile_path)),
|
||||
it.character
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TvShow.toLoadResponse(): TvSeriesLoadResponse {
|
||||
val episodes = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 }
|
||||
?.mapNotNull { season ->
|
||||
|
@ -112,57 +122,56 @@ open class TmdbProvider : MainAPI() {
|
|||
}
|
||||
}?.flatten() ?: listOf()
|
||||
|
||||
return TvSeriesLoadResponse(
|
||||
return newTvSeriesLoadResponse(
|
||||
this.name ?: this.original_name,
|
||||
getUrl(id, true),
|
||||
this@TmdbProvider.apiName,
|
||||
TvType.TvSeries,
|
||||
episodes,
|
||||
getImageUrl(this.poster_path),
|
||||
this.first_air_date?.let {
|
||||
episodes
|
||||
) {
|
||||
posterUrl = getImageUrl(poster_path)
|
||||
year = first_air_date?.let {
|
||||
Calendar.getInstance().apply {
|
||||
time = it
|
||||
}.get(Calendar.YEAR)
|
||||
},
|
||||
this.overview,
|
||||
null, // this.status
|
||||
this.external_ids?.imdb_id,
|
||||
this.rating,
|
||||
this.genres?.mapNotNull { it.name },
|
||||
this.episode_run_time?.average()?.toInt(),
|
||||
null,
|
||||
(this.recommendations ?: this.similar)?.results?.map { it.toSearchResponse() }
|
||||
)
|
||||
}
|
||||
plot = overview
|
||||
imdbId = external_ids?.imdb_id
|
||||
tags = genres?.mapNotNull { it.name }
|
||||
duration = episode_run_time?.average()?.toInt()
|
||||
rating = this@toLoadResponse.rating
|
||||
|
||||
recommendations = (this@toLoadResponse.recommendations
|
||||
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
|
||||
setActors(credits?.cast?.toList().toActors())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Movie.toLoadResponse(): MovieLoadResponse {
|
||||
println("TRAILRES::::::: ${this.similar} :::: ${this.recommendations} ")
|
||||
return MovieLoadResponse(
|
||||
this.title ?: this.original_title,
|
||||
getUrl(id, false),
|
||||
this@TmdbProvider.apiName,
|
||||
TvType.Movie,
|
||||
TmdbLink(
|
||||
return newMovieLoadResponse(
|
||||
this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink(
|
||||
this.imdb_id,
|
||||
this.id,
|
||||
null,
|
||||
null,
|
||||
this.title ?: this.original_title,
|
||||
).toJson(),
|
||||
getImageUrl(this.poster_path),
|
||||
this.release_date?.let {
|
||||
).toJson()
|
||||
) {
|
||||
posterUrl = getImageUrl(poster_path)
|
||||
year = release_date?.let {
|
||||
Calendar.getInstance().apply {
|
||||
time = it
|
||||
}.get(Calendar.YEAR)
|
||||
},
|
||||
this.overview,
|
||||
null,//this.status
|
||||
this.rating,
|
||||
this.genres?.mapNotNull { it.name },
|
||||
this.runtime,
|
||||
null,
|
||||
(this.recommendations ?: this.similar)?.results?.map { it.toSearchResponse() }
|
||||
)
|
||||
}
|
||||
plot = overview
|
||||
imdbId = external_ids?.imdb_id
|
||||
tags = genres?.mapNotNull { it.name }
|
||||
duration = runtime
|
||||
rating = this@toLoadResponse.rating
|
||||
|
||||
recommendations = (this@toLoadResponse.recommendations
|
||||
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
|
||||
setActors(credits?.cast?.toList().toActors())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(): HomePageResponse {
|
||||
|
@ -248,23 +257,38 @@ open class TmdbProvider : MainAPI() {
|
|||
return if (isTvSeries) {
|
||||
val body = tmdb.tvService().tv(id, "en-US").awaitResponse().body()
|
||||
val response = body?.toLoadResponse()
|
||||
if (response != null && response.recommendations.isNullOrEmpty()) {
|
||||
tmdb.tvService().recommendations(id, 1,"en-US").awaitResponse().body()?.let {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.movieproviders
|
|||
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
|
|
|
@ -26,7 +26,10 @@ class MeloMovieProvider : MainAPI() {
|
|||
//"mppa" for tags
|
||||
)
|
||||
|
||||
data class MeloMovieLink(val name: String, val link: String)
|
||||
data class MeloMovieLink(
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("link") val link: String
|
||||
)
|
||||
|
||||
override suspend fun quickSearch(query: String): List<SearchResponse> {
|
||||
return search(query)
|
||||
|
@ -106,7 +109,16 @@ class MeloMovieProvider : MainAPI() {
|
|||
): Boolean {
|
||||
val links = parseJson<List<MeloMovieLink>>(data)
|
||||
for (link in links) {
|
||||
callback.invoke(ExtractorLink(this.name, link.name, link.link, "", getQualityFromName(link.name), false))
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
link.name,
|
||||
link.link,
|
||||
"",
|
||||
getQualityFromName(link.name),
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -125,11 +137,13 @@ class MeloMovieProvider : MainAPI() {
|
|||
val type = findUsingRegex("var posttype = ([0-9]*)")?.toInt() ?: return null
|
||||
val titleInfo = document.selectFirst("div.movie_detail_title > div > div > h1")
|
||||
val title = titleInfo.ownText()
|
||||
val year = titleInfo.selectFirst("> a")?.text()?.replace("(", "")?.replace(")", "")?.toIntOrNull()
|
||||
val year =
|
||||
titleInfo.selectFirst("> a")?.text()?.replace("(", "")?.replace(")", "")?.toIntOrNull()
|
||||
val plot = document.selectFirst("div.col-lg-12 > p").text()
|
||||
|
||||
if (type == 1) { // MOVIE
|
||||
val serialize = document.selectFirst("table.accordion__list") ?: throw ErrorLoadingException("No links found")
|
||||
val serialize = document.selectFirst("table.accordion__list")
|
||||
?: throw ErrorLoadingException("No links found")
|
||||
return MovieLoadResponse(
|
||||
title,
|
||||
url,
|
||||
|
@ -143,15 +157,19 @@ class MeloMovieProvider : MainAPI() {
|
|||
)
|
||||
} else if (type == 2) {
|
||||
val episodes = ArrayList<TvSeriesEpisode>()
|
||||
val seasons = document.select("div.accordion__card") ?: throw ErrorLoadingException("No episodes found")
|
||||
val seasons = document.select("div.accordion__card")
|
||||
?: throw ErrorLoadingException("No episodes found")
|
||||
for (s in seasons) {
|
||||
val season =
|
||||
s.selectFirst("> div.card-header > button > span").text().replace("Season: ", "").toIntOrNull()
|
||||
s.selectFirst("> div.card-header > button > span").text()
|
||||
.replace("Season: ", "").toIntOrNull()
|
||||
val localEpisodes = s.select("> div.collapse > div > div > div.accordion__card")
|
||||
for (e in localEpisodes) {
|
||||
val episode =
|
||||
e.selectFirst("> div.card-header > button > span").text().replace("Episode: ", "").toIntOrNull()
|
||||
val links = e.selectFirst("> div.collapse > div > table.accordion__list") ?: continue
|
||||
e.selectFirst("> div.card-header > button > span").text()
|
||||
.replace("Episode: ", "").toIntOrNull()
|
||||
val links =
|
||||
e.selectFirst("> div.collapse > div > table.accordion__list") ?: continue
|
||||
val data = serializeData(links)
|
||||
episodes.add(TvSeriesEpisode(null, season, episode, data))
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
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() {
|
||||
override val mainUrl = "https://pelisflix.li"
|
||||
|
@ -16,6 +17,7 @@ class PelisflixProvider:MainAPI() {
|
|||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(): HomePageResponse {
|
||||
val items = ArrayList<HomePageList>()
|
||||
val urls = listOf(
|
||||
|
@ -47,6 +49,7 @@ class PelisflixProvider: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
|
||||
|
@ -95,12 +98,16 @@ class PelisflixProvider:MainAPI() {
|
|||
val desc2Regex = Regex("(G(e|é)nero:.*..)")
|
||||
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=\"", "")
|
||||
|
@ -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
|
||||
// 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,7 +217,8 @@ 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)),
|
||||
allowRedirects = false
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
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() {
|
||||
override val mainUrl = "https://seriesflix.video"
|
||||
|
@ -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,8 +83,7 @@ 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
|
||||
|
@ -100,7 +102,8 @@ 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=\"", "")
|
||||
|
@ -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,7 +213,8 @@ 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)),
|
||||
allowRedirects = false
|
||||
|
|
|
@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.movieproviders
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.setActorNames
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.setDuration
|
||||
import com.lagradost.cloudstream3.network.WebViewResolver
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
|
@ -100,13 +102,37 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() {
|
|||
val img = details.select("img.film-poster-img")
|
||||
val posterUrl = img.attr("src")
|
||||
val title = img.attr("title")
|
||||
|
||||
/*
|
||||
val year = Regex("""[Rr]eleased:\s*(\d{4})""").find(
|
||||
document.select("div.elements").text()
|
||||
)?.groupValues?.get(1)?.toIntOrNull()
|
||||
val duration = Regex("""[Dd]uration:\s*(\d*)""").find(
|
||||
document.select("div.elements").text()
|
||||
)?.groupValues?.get(1)?.trim()?.plus(" min")
|
||||
|
||||
)?.groupValues?.get(1)?.trim()?.plus(" min")*/
|
||||
var duration = document.selectFirst(".fs-item > .duration").text()?.trim()
|
||||
var year: Int? = null
|
||||
var tags: List<String>? = null
|
||||
var cast: List<String>? = null
|
||||
document.select("div.elements > .row > div > .row-line")?.forEach { element ->
|
||||
val type = element?.select(".type")?.text() ?: return@forEach
|
||||
when {
|
||||
type.contains("Released") -> {
|
||||
year = Regex("\\d+").find(
|
||||
element.ownText() ?: return@forEach
|
||||
)?.groupValues?.firstOrNull()?.toIntOrNull()
|
||||
}
|
||||
type.contains("Genre") -> {
|
||||
tags = element.select("a")?.mapNotNull { it.text() }
|
||||
}
|
||||
type.contains("Cast") -> {
|
||||
cast = element.select("a")?.mapNotNull { it.text() }
|
||||
}
|
||||
type.contains("Duration") -> {
|
||||
duration = duration ?: element.ownText()?.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
val plot = details.select("div.description").text().replace("Overview:", "").trim()
|
||||
|
||||
val isMovie = url.contains("/movie/")
|
||||
|
@ -156,6 +182,8 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() {
|
|||
this.posterUrl = posterUrl
|
||||
this.plot = plot
|
||||
setDuration(duration)
|
||||
setActorNames(cast)
|
||||
this.tags = tags
|
||||
this.recommendations = recommendations
|
||||
}
|
||||
} else {
|
||||
|
@ -201,6 +229,8 @@ class SflixProvider(providerUrl: String, providerName: String) : MainAPI() {
|
|||
this.year = year
|
||||
this.plot = plot
|
||||
setDuration(duration)
|
||||
setActorNames(cast)
|
||||
this.tags = tags
|
||||
this.recommendations = recommendations
|
||||
}
|
||||
}
|
||||
|
|
|
@ -779,10 +779,10 @@ class HomeFragment : Fragment() {
|
|||
home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { view, _, scrollY, _, oldScrollY ->
|
||||
val dy = scrollY - oldScrollY
|
||||
if (dy > 0) { //check for scroll down
|
||||
home_api_fab?.hide()
|
||||
home_api_fab?.shrink() // hide
|
||||
} else if (dy < -5) {
|
||||
if (view?.context?.isTvSettings() == false) {
|
||||
home_api_fab?.show()
|
||||
home_api_fab?.extend() // show
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.ActorData
|
||||
import com.lagradost.cloudstream3.ActorRole
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
import kotlinx.android.synthetic.main.cast_item.view.*
|
||||
|
||||
class ActorAdaptor(
|
||||
private val actors: MutableList<ActorData>,
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return CardViewHolder(
|
||||
LayoutInflater.from(parent.context).inflate(R.layout.cast_item, parent, false),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is CardViewHolder -> {
|
||||
holder.bind(actors[position])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return actors.size
|
||||
}
|
||||
|
||||
fun updateList(newList: List<ActorData>) {
|
||||
val diffResult = DiffUtil.calculateDiff(
|
||||
ActorDiffCallback(this.actors, newList)
|
||||
)
|
||||
|
||||
actors.clear()
|
||||
actors.addAll(newList)
|
||||
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
private class CardViewHolder
|
||||
constructor(
|
||||
itemView: View,
|
||||
) :
|
||||
RecyclerView.ViewHolder(itemView) {
|
||||
private val actorImage: ImageView = itemView.actor_image
|
||||
private val actorName: TextView = itemView.actor_name
|
||||
private val actorExtra: TextView = itemView.actor_extra
|
||||
private val voiceActorImage: ImageView = itemView.voice_actor_image
|
||||
private val voiceActorImageHolder: View = itemView.voice_actor_image_holder
|
||||
private val voiceActorName: TextView = itemView.voice_actor_name
|
||||
|
||||
fun bind(card: ActorData) {
|
||||
actorImage.setImage(card.actor.image)
|
||||
actorName.text = card.actor.name
|
||||
card.role?.let {
|
||||
actorExtra.context?.getString(
|
||||
when (it) {
|
||||
ActorRole.Main -> {
|
||||
R.string.actor_main
|
||||
}
|
||||
ActorRole.Supporting -> {
|
||||
R.string.actor_supporting
|
||||
}
|
||||
}
|
||||
)?.let { text ->
|
||||
actorExtra.isVisible = true
|
||||
actorExtra.text = text
|
||||
}
|
||||
} ?: card.roleString?.let {
|
||||
actorExtra.isVisible = true
|
||||
actorExtra.text = it
|
||||
} ?: run {
|
||||
actorExtra.isVisible = false
|
||||
}
|
||||
|
||||
if (card.voiceActor == null) {
|
||||
voiceActorImageHolder.isVisible = false
|
||||
voiceActorName.isVisible = false
|
||||
} else {
|
||||
voiceActorName.text = card.voiceActor.name
|
||||
voiceActorImageHolder.isVisible = voiceActorImage.setImage(card.voiceActor.image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ActorDiffCallback(
|
||||
private val oldList: List<ActorData>,
|
||||
private val newList: List<ActorData>
|
||||
) :
|
||||
DiffUtil.Callback() {
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
oldList[oldItemPosition].actor.name == newList[newItemPosition].actor.name
|
||||
|
||||
override fun getOldListSize() = oldList.size
|
||||
|
||||
override fun getNewListSize() = newList.size
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
oldList[oldItemPosition] == newList[newItemPosition]
|
||||
}
|
|
@ -391,6 +391,10 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
//requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR
|
||||
downloadButton?.dispose()
|
||||
updateUIListener = null
|
||||
result_cast_items?.let {
|
||||
PanelsChildGestureRegionObserver.Provider.get().unregister(it)
|
||||
}
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
@ -482,6 +486,22 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
setFormatText(result_meta_duration, R.string.duration_format, duration)
|
||||
}
|
||||
|
||||
private fun setShow(showStatus : ShowStatus?) {
|
||||
val status = when (showStatus) {
|
||||
null -> null
|
||||
ShowStatus.Ongoing -> R.string.status_ongoing
|
||||
ShowStatus.Completed -> R.string.status_completed
|
||||
}
|
||||
|
||||
if (status == null) {
|
||||
result_meta_status?.isVisible = false
|
||||
} else {
|
||||
context?.getString(status)?.let {
|
||||
result_meta_status?.text = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setYear(year: Int?) {
|
||||
setFormatText(result_meta_year, R.string.year_format, year)
|
||||
}
|
||||
|
@ -490,6 +510,27 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f))
|
||||
}
|
||||
|
||||
private fun setActors(actors: List<ActorData>?) {
|
||||
if (actors.isNullOrEmpty()) {
|
||||
result_cast_text?.isVisible = false
|
||||
result_cast_items?.isVisible = false
|
||||
} else {
|
||||
val isImage = actors.first().actor.image != null
|
||||
if (isImage) {
|
||||
(result_cast_items?.adapter as ActorAdaptor?)?.apply {
|
||||
updateList(actors)
|
||||
}
|
||||
result_cast_text?.isVisible = false
|
||||
result_cast_items?.isVisible = true
|
||||
} else {
|
||||
result_cast_text?.isVisible = true
|
||||
result_cast_items?.isVisible = false
|
||||
setFormatText(result_cast_text, R.string.cast_format,
|
||||
actors.joinToString { it.actor.name })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRecommendations(rec: List<SearchResponse>?) {
|
||||
val isInvalid = rec.isNullOrEmpty()
|
||||
result_recommendations?.isGone = isInvalid
|
||||
|
@ -540,6 +581,10 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
result_cast_items?.let {
|
||||
PanelsChildGestureRegionObserver.Provider.get().register(it)
|
||||
}
|
||||
result_cast_items?.adapter = ActorAdaptor(mutableListOf())
|
||||
fixGrid()
|
||||
result_recommendations?.spanCount = 3
|
||||
result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
|
||||
|
@ -1281,22 +1326,17 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
}
|
||||
}
|
||||
|
||||
val metadataInfoArray = ArrayList<Pair<Int, String>>()
|
||||
if (d is AnimeLoadResponse) {
|
||||
val status = when (d.showStatus) {
|
||||
null -> null
|
||||
ShowStatus.Ongoing -> R.string.status_ongoing
|
||||
ShowStatus.Completed -> R.string.status_completed
|
||||
val showStatus = when (d) {
|
||||
is TvSeriesLoadResponse -> d.showStatus
|
||||
is AnimeLoadResponse -> d.showStatus
|
||||
else -> null
|
||||
}
|
||||
if (status != null) {
|
||||
metadataInfoArray.add(Pair(R.string.status, getString(status)))
|
||||
}
|
||||
}
|
||||
|
||||
setShow(showStatus)
|
||||
setDuration(d.duration)
|
||||
setYear(d.year)
|
||||
setRating(d.rating)
|
||||
setRecommendations(d.recommendations)
|
||||
setActors(d.actors)
|
||||
|
||||
result_meta_site?.text = d.apiName
|
||||
|
||||
|
@ -1509,7 +1549,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||
val showFillers =
|
||||
settingsManager.getBoolean(ctx.getString(R.string.show_fillers_key), true)
|
||||
settingsManager.getBoolean(ctx.getString(R.string.show_fillers_key), false)
|
||||
|
||||
val tempUrl = url
|
||||
if (tempUrl != null) {
|
||||
|
@ -1537,6 +1577,13 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
|
@ -17,7 +18,10 @@ const val RESULT_SEASON = "result_season"
|
|||
const val RESULT_DUB = "result_dub"
|
||||
|
||||
object DataStoreHelper {
|
||||
data class PosDur(val position: Long, val duration: Long)
|
||||
data class PosDur(
|
||||
@JsonProperty("position") val position: Long,
|
||||
@JsonProperty("duration") val duration: Long
|
||||
)
|
||||
|
||||
fun PosDur.fixVisual(): PosDur {
|
||||
if (duration <= 0) return PosDur(0, duration)
|
||||
|
@ -29,31 +33,31 @@ object DataStoreHelper {
|
|||
}
|
||||
|
||||
data class BookmarkedData(
|
||||
override val id: Int?,
|
||||
val bookmarkedTime: Long,
|
||||
val latestUpdatedTime: Long,
|
||||
override val name: String,
|
||||
override val url: String,
|
||||
override val apiName: String,
|
||||
override val type: TvType,
|
||||
override val posterUrl: String?,
|
||||
val year: Int?,
|
||||
@JsonProperty("id") override val id: Int?,
|
||||
@JsonProperty("bookmarkedTime") val bookmarkedTime: Long,
|
||||
@JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long,
|
||||
@JsonProperty("name") override val name: String,
|
||||
@JsonProperty("url") override val url: String,
|
||||
@JsonProperty("apiName") override val apiName: String,
|
||||
@JsonProperty("type") override val type: TvType,
|
||||
@JsonProperty("posterUrl") override val posterUrl: String?,
|
||||
@JsonProperty("year") val year: Int?,
|
||||
) : SearchResponse
|
||||
|
||||
data class ResumeWatchingResult(
|
||||
override val name: String,
|
||||
override val url: String,
|
||||
override val apiName: String,
|
||||
override val type: TvType,
|
||||
override val posterUrl: String?,
|
||||
@JsonProperty("name") override val name: String,
|
||||
@JsonProperty("url") override val url: String,
|
||||
@JsonProperty("apiName") override val apiName: String,
|
||||
@JsonProperty("type") override val type: TvType,
|
||||
@JsonProperty("posterUrl") override val posterUrl: String?,
|
||||
|
||||
val watchPos: PosDur?,
|
||||
@JsonProperty("watchPos") val watchPos: PosDur?,
|
||||
|
||||
override val id: Int?,
|
||||
val parentId: Int?,
|
||||
val episode: Int?,
|
||||
val season: Int?,
|
||||
val isFromDownload: Boolean,
|
||||
@JsonProperty("id") override val id: Int?,
|
||||
@JsonProperty("parentId") val parentId: Int?,
|
||||
@JsonProperty("episode") val episode: Int?,
|
||||
@JsonProperty("season") val season: Int?,
|
||||
@JsonProperty("isFromDownload") val isFromDownload: Boolean,
|
||||
) : SearchResponse
|
||||
|
||||
var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
|
||||
|
@ -148,7 +152,13 @@ object DataStoreHelper {
|
|||
}
|
||||
|
||||
fun getResultWatchState(id: Int): WatchType {
|
||||
return WatchType.fromInternalId(getKey<Int>("$currentAccount/$RESULT_WATCH_STATE", id.toString(), null))
|
||||
return WatchType.fromInternalId(
|
||||
getKey<Int>(
|
||||
"$currentAccount/$RESULT_WATCH_STATE",
|
||||
id.toString(),
|
||||
null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun getResultSeason(id: Int): Int {
|
||||
|
|
|
@ -1,37 +1,38 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||
|
||||
object VideoDownloadHelper {
|
||||
data class DownloadEpisodeCached(
|
||||
val name: String?,
|
||||
val poster: String?,
|
||||
val episode: Int,
|
||||
val season: Int?,
|
||||
override val id: Int,
|
||||
val parentId: Int,
|
||||
val rating: Int?,
|
||||
val description: String?,
|
||||
val cacheTime: Long,
|
||||
@JsonProperty("name") val name: String?,
|
||||
@JsonProperty("poster") val poster: String?,
|
||||
@JsonProperty("episode") val episode: Int,
|
||||
@JsonProperty("season") val season: Int?,
|
||||
@JsonProperty("id") override val id: Int,
|
||||
@JsonProperty("parentId") val parentId: Int,
|
||||
@JsonProperty("rating") val rating: Int?,
|
||||
@JsonProperty("description") val description: String?,
|
||||
@JsonProperty("cacheTime") val cacheTime: Long,
|
||||
) : EasyDownloadButton.IMinimumData
|
||||
|
||||
data class DownloadHeaderCached(
|
||||
val apiName: String,
|
||||
val url: String,
|
||||
val type: TvType,
|
||||
val name: String,
|
||||
val poster: String?,
|
||||
val id: Int,
|
||||
val cacheTime: Long,
|
||||
@JsonProperty("apiName") val apiName: String,
|
||||
@JsonProperty("url") val url: String,
|
||||
@JsonProperty("type") val type: TvType,
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("poster") val poster: String?,
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("cacheTime") val cacheTime: Long,
|
||||
)
|
||||
|
||||
data class ResumeWatching(
|
||||
val parentId: Int,
|
||||
val episodeId: Int,
|
||||
val episode: Int?,
|
||||
val season: Int?,
|
||||
val updateTime : Long,
|
||||
val isFromDownload: Boolean,
|
||||
@JsonProperty("parentId") val parentId: Int,
|
||||
@JsonProperty("episodeId") val episodeId: Int,
|
||||
@JsonProperty("episode") val episode: Int?,
|
||||
@JsonProperty("season") val season: Int?,
|
||||
@JsonProperty("updateTime") val updateTime: Long,
|
||||
@JsonProperty("isFromDownload") val isFromDownload: Boolean,
|
||||
)
|
||||
}
|
|
@ -108,44 +108,44 @@ object VideoDownloadManager {
|
|||
}
|
||||
|
||||
data class DownloadEpisodeMetadata(
|
||||
val id: Int,
|
||||
val mainName: String,
|
||||
val sourceApiName: String?,
|
||||
val poster: String?,
|
||||
val name: String?,
|
||||
val season: Int?,
|
||||
val episode: Int?
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("mainName") val mainName: String,
|
||||
@JsonProperty("sourceApiName") val sourceApiName: String?,
|
||||
@JsonProperty("poster") val poster: String?,
|
||||
@JsonProperty("name") val name: String?,
|
||||
@JsonProperty("season") val season: Int?,
|
||||
@JsonProperty("episode") val episode: Int?
|
||||
)
|
||||
|
||||
data class DownloadItem(
|
||||
val source: String?,
|
||||
val folder: String?,
|
||||
val ep: DownloadEpisodeMetadata,
|
||||
val links: List<ExtractorLink>,
|
||||
@JsonProperty("source") val source: String?,
|
||||
@JsonProperty("folder") val folder: String?,
|
||||
@JsonProperty("ep") val ep: DownloadEpisodeMetadata,
|
||||
@JsonProperty("links") val links: List<ExtractorLink>,
|
||||
)
|
||||
|
||||
data class DownloadResumePackage(
|
||||
val item: DownloadItem,
|
||||
val linkIndex: Int?,
|
||||
@JsonProperty("item") val item: DownloadItem,
|
||||
@JsonProperty("linkIndex") val linkIndex: Int?,
|
||||
)
|
||||
|
||||
data class DownloadedFileInfo(
|
||||
val totalBytes: Long,
|
||||
val relativePath: String,
|
||||
val displayName: String,
|
||||
val extraInfo: String? = null,
|
||||
val basePath: String? = null // null is for legacy downloads. See getDefaultPath()
|
||||
@JsonProperty("totalBytes") val totalBytes: Long,
|
||||
@JsonProperty("relativePath") val relativePath: String,
|
||||
@JsonProperty("displayName") val displayName: String,
|
||||
@JsonProperty("extraInfo") val extraInfo: String? = null,
|
||||
@JsonProperty("basePath") val basePath: String? = null // null is for legacy downloads. See getDefaultPath()
|
||||
)
|
||||
|
||||
data class DownloadedFileInfoResult(
|
||||
val fileLength: Long,
|
||||
val totalBytes: Long,
|
||||
val path: Uri,
|
||||
@JsonProperty("fileLength") val fileLength: Long,
|
||||
@JsonProperty("totalBytes") val totalBytes: Long,
|
||||
@JsonProperty("path") val path: Uri,
|
||||
)
|
||||
|
||||
data class DownloadQueueResumePackage(
|
||||
val index: Int,
|
||||
val pkg: DownloadResumePackage,
|
||||
@JsonProperty("index") val index: Int,
|
||||
@JsonProperty("pkg") val pkg: DownloadResumePackage,
|
||||
)
|
||||
|
||||
private const val SUCCESS_DOWNLOAD_DONE = 1
|
||||
|
|
|
@ -8,10 +8,19 @@ import com.lagradost.cloudstream3.R
|
|||
import kotlin.math.max
|
||||
|
||||
class FlowLayout : ViewGroup {
|
||||
var itemSpacing : Int = 0
|
||||
|
||||
constructor(context: Context?) : super(context)
|
||||
|
||||
@JvmOverloads
|
||||
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr)
|
||||
//@JvmOverloads
|
||||
//constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr)
|
||||
|
||||
@SuppressLint("CustomViewStyleable")
|
||||
internal constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) {
|
||||
val t = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout)
|
||||
itemSpacing = t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_itemSpacing, 0);
|
||||
t.recycle()
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val realWidth = MeasureSpec.getSize(widthMeasureSpec)
|
||||
|
@ -29,13 +38,13 @@ class FlowLayout : ViewGroup {
|
|||
//check if child can be placed in the current row, else go to next line
|
||||
if (currentChildHookPointx + childWidth > realWidth) {
|
||||
//new line
|
||||
currentWidth = Math.max(currentWidth, currentChildHookPointx)
|
||||
currentWidth = max(currentWidth, currentChildHookPointx)
|
||||
|
||||
//reset for new line
|
||||
currentChildHookPointx = 0
|
||||
currentChildHookPointy += childHeight
|
||||
}
|
||||
val nextChildHookPointx = currentChildHookPointx + childWidth
|
||||
val nextChildHookPointx = currentChildHookPointx + childWidth + if(childWidth == 0) 0 else itemSpacing
|
||||
val nextChildHookPointy = currentChildHookPointy
|
||||
currentHeight = max(currentHeight, currentChildHookPointy + childHeight)
|
||||
val lp = child.layoutParams as LayoutParams
|
||||
|
@ -44,7 +53,7 @@ class FlowLayout : ViewGroup {
|
|||
currentChildHookPointx = nextChildHookPointx
|
||||
currentChildHookPointy = nextChildHookPointy
|
||||
}
|
||||
currentWidth = Math.max(currentChildHookPointx, currentWidth)
|
||||
currentWidth = max(currentChildHookPointx, currentWidth)
|
||||
setMeasuredDimension(resolveSize(currentWidth, widthMeasureSpec),
|
||||
resolveSize(currentHeight, heightMeasureSpec))
|
||||
}
|
||||
|
@ -83,7 +92,7 @@ class FlowLayout : ViewGroup {
|
|||
@SuppressLint("CustomViewStyleable")
|
||||
internal constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) {
|
||||
val t = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout)
|
||||
spacing = 0 //t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_layout_space, 0);
|
||||
spacing = 0//t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_itemSpacing, 0);
|
||||
t.recycle()
|
||||
}
|
||||
|
||||
|
|
107
app/src/main/res/layout/cast_item.xml
Normal file
107
app/src/main/res/layout/cast_item.xml
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
||||
android:nextFocusLeft="@id/episode_poster"
|
||||
android:nextFocusRight="@id/result_episode_download"
|
||||
android:id="@+id/episode_holder"
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||
app:cardBackgroundColor="?attr/boxItemBackground"
|
||||
|
||||
android:foreground="@drawable/outline_drawable"
|
||||
android:layout_marginEnd="10dp">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="100dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content">
|
||||
<!--app:cardCornerRadius="@dimen/roundedImageRadius"-->
|
||||
<FrameLayout
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
app:cardCornerRadius="70dp"
|
||||
android:layout_width="70dp"
|
||||
android:layout_height="70dp"
|
||||
android:foreground="@drawable/outline_drawable">
|
||||
|
||||
<ImageView
|
||||
android:nextFocusLeft="@id/result_episode_download"
|
||||
android:nextFocusRight="@id/episode_holder"
|
||||
|
||||
android:id="@+id/actor_image"
|
||||
tools:src="@drawable/example_poster"
|
||||
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/episode_poster_img_des" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/voice_actor_image_holder"
|
||||
android:layout_gravity="end|bottom"
|
||||
app:cardCornerRadius="40dp"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:foreground="@drawable/outline_drawable">
|
||||
|
||||
<ImageView
|
||||
android:nextFocusLeft="@id/result_episode_download"
|
||||
android:nextFocusRight="@id/episode_holder"
|
||||
|
||||
android:id="@+id/voice_actor_image"
|
||||
tools:src="@drawable/example_poster"
|
||||
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/episode_poster_img_des" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:padding="10dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:gravity="center_horizontal"
|
||||
android:id="@+id/actor_name"
|
||||
tools:text="Ackerman, Mikasa"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:gravity="center_horizontal"
|
||||
android:id="@+id/voice_actor_name"
|
||||
tools:text="voiceactor"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:gravity="center_horizontal"
|
||||
android:id="@+id/actor_extra"
|
||||
tools:text="Main"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -506,8 +506,10 @@
|
|||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:text="@string/home_source"
|
||||
android:id="@+id/home_api_fab"
|
||||
app:icon="@drawable/ic_baseline_filter_list_24"
|
||||
style="@style/ExtendedFloatingActionButton"
|
||||
android:textColor="?attr/textColor"
|
||||
tools:ignore="ContentDescription" />
|
||||
</FrameLayout>
|
|
@ -311,6 +311,7 @@
|
|||
android:layout_height="wrap_content" />
|
||||
|
||||
<com.lagradost.cloudstream3.widget.FlowLayout
|
||||
app:itemSpacing="10dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
@ -322,56 +323,28 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/result_meta_type"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:minHeight="24dp"
|
||||
android:gravity="center"
|
||||
|
||||
tools:text="Movie"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
style="@style/ResultInfoText"
|
||||
tools:text="Movie" />
|
||||
|
||||
<TextView
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:minHeight="24dp"
|
||||
android:gravity="center"
|
||||
|
||||
tools:text="2022"
|
||||
android:id="@+id/result_meta_year"
|
||||
android:layout_marginStart="10dp"
|
||||
tools:text="2021"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textColor="?attr/white"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
style="@style/ResultInfoText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_meta_rating"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:minHeight="24dp"
|
||||
android:gravity="center"
|
||||
|
||||
tools:text="Rated: 8.5/10.0"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
style="@style/ResultInfoText"
|
||||
tools:text="Rated: 8.5/10.0" />
|
||||
|
||||
<TextView
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:minHeight="24dp"
|
||||
android:gravity="center"
|
||||
android:id="@+id/result_meta_duration"
|
||||
android:id="@+id/result_meta_status"
|
||||
style="@style/ResultInfoText"
|
||||
tools:text="Ongoing" />
|
||||
|
||||
tools:text="121min"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
<TextView
|
||||
style="@style/ResultInfoText"
|
||||
android:id="@+id/result_meta_duration"
|
||||
tools:text="121min" />
|
||||
</com.lagradost.cloudstream3.widget.FlowLayout>
|
||||
|
||||
<FrameLayout
|
||||
|
@ -428,6 +401,28 @@
|
|||
<requestFocus />
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<TextView
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:id="@+id/result_cast_text"
|
||||
android:textSize="15sp"
|
||||
tools:text="Cast: Joe Ligma"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
tools:itemCount="2"
|
||||
android:fadingEdge="horizontal"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:id="@+id/result_cast_items"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/cast_item" />
|
||||
|
||||
<TextView
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:id="@+id/result_vpn"
|
||||
|
@ -564,6 +559,7 @@
|
|||
android:text="@string/resume"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
android:layout_width="match_parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_marginBottom="10dp"
|
||||
android:nextFocusUp="@id/result_bookmark_button"
|
||||
|
@ -607,6 +603,7 @@
|
|||
android:layout_weight="1"
|
||||
android:visibility="visible"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_resume_series_progress_text"
|
||||
android:layout_gravity="center"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="FlowLayout_Layout"/>
|
||||
<declare-styleable name="FlowLayout_Layout">
|
||||
<attr format="dimension" name="itemSpacing" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="FlowLayout_Layout_layout_space" />
|
||||
|
||||
<declare-styleable name="CustomCast">
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
<string name="rating_format" translatable="false" formatted="true">%.1f/10.0</string>
|
||||
<string name="year_format" translatable="false" formatted="true">%d</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="cast_format" formatted="true">Cast: %s</string>
|
||||
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
|
@ -384,4 +385,7 @@
|
|||
<string name="player_loaded_subtitles" formatted="true">Loaded %s</string>
|
||||
<string name="player_load_subtitles">Load from file</string>
|
||||
<string name="downloaded_file">Downloaded file</string>
|
||||
<string name="actor_main">Main</string>
|
||||
<string name="actor_supporting">Supporting</string>
|
||||
<string name="home_source">Source</string>
|
||||
</resources>
|
||||
|
|
|
@ -229,6 +229,19 @@
|
|||
<item name="android:fontFamily">@font/google_sans</item>
|
||||
</style>
|
||||
|
||||
<style name="ResultInfoText">
|
||||
<item name="android:layout_gravity">center_vertical</item>
|
||||
<item name="textColor">?attr/white</item>
|
||||
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:minHeight">24dp</item>
|
||||
<item name="android:minWidth">0dp</item>
|
||||
<!-- <item name="android:paddingStart">5dp</item>
|
||||
<item name="android:paddingEnd">5dp</item>-->
|
||||
</style>
|
||||
|
||||
<style name="AppMaterialButtonStyle" parent="Widget.MaterialComponents.Button">
|
||||
<item name="android:fontFamily">@font/google_sans</item>
|
||||
</style>
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
android:key="@string/show_fillers_key"
|
||||
android:icon="@drawable/ic_baseline_skip_next_24"
|
||||
android:title="@string/show_fillers_settings"
|
||||
android:defaultValue="true" />
|
||||
android:defaultValue="false" />
|
||||
<Preference
|
||||
android:key="@string/dns_key"
|
||||
android:title="@string/dns_pref"
|
||||
|
|
Loading…
Reference in a new issue