small cleanup

This commit is contained in:
LagradOst 2021-08-19 22:05:18 +02:00
parent 1da670af80
commit 29d5f3848a
26 changed files with 215 additions and 542 deletions

View file

@ -65,20 +65,24 @@ object APIHolder {
fun Activity.getApiSettings(): HashSet<String> { fun Activity.getApiSettings(): HashSet<String> {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val hashSet = HashSet<String>()
hashSet.addAll(apis.map { it.name })
return settingsManager.getStringSet( return settingsManager.getStringSet(
this.getString(R.string.search_providers_list_key), this.getString(R.string.search_providers_list_key),
setOf(apis[defProvider].name) hashSet
)?.toHashSet() ?: hashSetOf(apis[defProvider].name) )?.toHashSet() ?: hashSet
} }
fun Activity.getApiTypeSettings(): HashSet<TvType> { fun Activity.getApiTypeSettings(): HashSet<TvType> {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val list = settingsManager.getStringSet(
this.getString(R.string.search_types_list_key),
setOf(apis[defProvider].name)
)
val hashSet = HashSet<TvType>() val hashSet = HashSet<TvType>()
hashSet.addAll(TvType.values()) hashSet.addAll(TvType.values())
val list = settingsManager.getStringSet(
this.getString(R.string.search_types_list_key),
hashSet.map { it.name }.toMutableSet()
)
if(list.isNullOrEmpty()) return hashSet if(list.isNullOrEmpty()) return hashSet
val names = TvType.values().map { it.name }.toHashSet() val names = TvType.values().map { it.name }.toHashSet()

View file

@ -296,6 +296,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
/*thread { /*thread {
createISO() createISO()
}*/ }*/
var providersString = "Current providers are:\n"
for (api in apis) {
providersString += "+ ${api.mainUrl}\n"
}
println(providersString)
handleAppIntent(intent) handleAppIntent(intent)
thread { thread {

View file

@ -11,35 +11,39 @@ import org.jsoup.Jsoup
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class AnimePaheProvider : MainAPI() { class AnimePaheProvider : MainAPI() {
companion object { companion object {
const val MAIN_URL = "https://animepahe.com"
var cookies = CookieJar() var cookies = CookieJar()
private fun getType(t: String): TvType { private fun getType(t: String): TvType {
return if (t.contains("OVA") || t.contains("Special")) TvType.ONA return if (t.contains("OVA") || t.contains("Special")) TvType.ONA
else if (t.contains("Movie")) TvType.AnimeMovie else if (t.contains("Movie")) TvType.AnimeMovie
else TvType.Anime else TvType.Anime
} }
fun generateSession(): Boolean { fun generateSession(): Boolean {
if (cookies.entries.size != 0) return true if (cookies.entries.size != 0) return true
return try { return try {
val response = khttp.get("https://animepahe.com/") val response = khttp.get("$MAIN_URL/")
cookies = response.cookies cookies = response.cookies
true true
} catch (e: Exception) { } catch (e: Exception) {
false false
} }
} }
val YTSM = "ysmm = '([^']+)".toRegex() val YTSM = "ysmm = '([^']+)".toRegex()
val KWIK_PARAMS_RE = Regex("""\(\"(\w+)\",\d+,\"(\w+)\",(\d+),(\d+),\d+\)""") val KWIK_PARAMS_RE = Regex("""\("(\w+)",\d+,"(\w+)",(\d+),(\d+),\d+\)""")
val KWIK_D_URL = Regex("action=\"([^\"]+)\"") val KWIK_D_URL = Regex("action=\"([^\"]+)\"")
val KWIK_D_TOKEN = Regex("value=\"([^\"]+)\"") val KWIK_D_TOKEN = Regex("value=\"([^\"]+)\"")
val YOUTUBE_VIDEO_LINK = Regex("""(^(?:https?:)?(?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube(?:\-nocookie)?\.(?:[A-Za-z]{2,4}|[A-Za-z]{2,3}\.[A-Za-z]{2})\/)(?:watch|embed\/|vi?\/)*(?:\?[\w=&]*vi?=)?[^#&\?\/]{11}.*${'$'})""") val YOUTUBE_VIDEO_LINK =
Regex("""(^(?:https?:)?(?://)?(?:www\.)?(?:youtu\.be/|youtube(?:-nocookie)?\.(?:[A-Za-z]{2,4}|[A-Za-z]{2,3}\.[A-Za-z]{2})/)(?:watch|embed/|vi?/)*(?:\?[\w=&]*vi?=)?[^#&?/]{11}.*${'$'})""")
} }
override val mainUrl: String override val mainUrl: String
get() = "https://animepahe.com" get() = MAIN_URL
override val name: String override val name: String
get() = "AnimePahe" get() = "AnimePahe"
override val hasQuickSearch: Boolean override val hasQuickSearch: Boolean
@ -54,19 +58,19 @@ class AnimePaheProvider : MainAPI() {
TvType.ONA TvType.ONA
) )
override fun getMainPage(): HomePageResponse? { override fun getMainPage(): HomePageResponse {
data class Data ( data class Data(
@JsonProperty("id") val id : Int, @JsonProperty("id") val id: Int,
@JsonProperty("anime_id") val animeId : Int, @JsonProperty("anime_id") val animeId: Int,
@JsonProperty("anime_title") val animeTitle : String, @JsonProperty("anime_title") val animeTitle: String,
@JsonProperty("episode") val episode : Int, @JsonProperty("episode") val episode: Int,
@JsonProperty("snapshot") val snapshot : String, @JsonProperty("snapshot") val snapshot: String,
@JsonProperty("created_at") val createdAt : String, @JsonProperty("created_at") val createdAt: String,
) )
data class AnimePaheLatestReleases ( data class AnimePaheLatestReleases(
@JsonProperty("total") val total : Int, @JsonProperty("total") val total: Int,
@JsonProperty("data") val data : List<Data> @JsonProperty("data") val data: List<Data>
) )
val urls = listOf( val urls = listOf(
@ -98,36 +102,36 @@ class AnimePaheProvider : MainAPI() {
e.printStackTrace() e.printStackTrace()
} }
} }
if(items.size <= 0) throw ErrorLoadingException() if (items.size <= 0) throw ErrorLoadingException()
return HomePageResponse(items) return HomePageResponse(items)
} }
override fun search(query: String): ArrayList<SearchResponse> { override fun search(query: String): ArrayList<SearchResponse> {
data class AnimePaheSearchData ( data class AnimePaheSearchData(
@JsonProperty("id") val id : Int, @JsonProperty("id") val id: Int,
@JsonProperty("slug") val slug : String, @JsonProperty("slug") val slug: String,
@JsonProperty("title") val title : String, @JsonProperty("title") val title: String,
@JsonProperty("type") val type : String, @JsonProperty("type") val type: String,
@JsonProperty("episodes") val episodes : Int, @JsonProperty("episodes") val episodes: Int,
@JsonProperty("status") val status : String, @JsonProperty("status") val status: String,
@JsonProperty("season") val season : String, @JsonProperty("season") val season: String,
@JsonProperty("year") val year : Int, @JsonProperty("year") val year: Int,
@JsonProperty("score") val score : Double, @JsonProperty("score") val score: Double,
@JsonProperty("poster") val poster : String, @JsonProperty("poster") val poster: String,
@JsonProperty("session") val session : String, @JsonProperty("session") val session: String,
@JsonProperty("relevance") val relevance : String @JsonProperty("relevance") val relevance: String
) )
data class AnimePaheSearch ( data class AnimePaheSearch(
@JsonProperty("total") val total : Int, @JsonProperty("total") val total: Int,
@JsonProperty("data") val data : List<AnimePaheSearchData> @JsonProperty("data") val data: List<AnimePaheSearchData>
) )
val url = "https://animepahe.com/api?m=search&l=8&q=$query" val url = "$mainUrl/api?m=search&l=8&q=$query"
val headers = mapOf("referer" to "https://animepahe.com/") val headers = mapOf("referer" to "$mainUrl/")
val req = khttp.get(url, headers=headers) val req = khttp.get(url, headers = headers)
val data = req.let { mapper.readValue<AnimePaheSearch>(it.text) } val data = req.let { mapper.readValue<AnimePaheSearch>(it.text) }
return ArrayList(data.data.map { return ArrayList(data.data.map {
@ -172,11 +176,11 @@ class AnimePaheProvider : MainAPI() {
private fun generateListOfEpisodes(link: String): ArrayList<AnimeEpisode> { private fun generateListOfEpisodes(link: String): ArrayList<AnimeEpisode> {
try { try {
val attrs = link.split('/') val attrs = link.split('/')
val id = attrs[attrs.size - 1] val id = attrs[attrs.size - 1]
val uri = "https://animepahe.com/api?m=release&id=$id&sort=episode_asc&page=1" val uri = "$mainUrl/api?m=release&id=$id&sort=episode_asc&page=1"
val headers = mapOf("referer" to "https://animepahe.com/") val headers = mapOf("referer" to "$mainUrl/")
val req = khttp.get(uri, headers = headers) val req = khttp.get(uri, headers = headers)
val data = req.let { mapper.readValue<AnimePaheAnimeData>(it.text) } val data = req.let { mapper.readValue<AnimePaheAnimeData>(it.text) }
@ -188,7 +192,7 @@ class AnimePaheProvider : MainAPI() {
val episodes = ArrayList<AnimeEpisode>() val episodes = ArrayList<AnimeEpisode>()
fun getEpisodeTitle(k: AnimeData): String { fun getEpisodeTitle(k: AnimeData): String {
return if (k.title.length == 0) { return if (k.title.isEmpty()) {
"Episode ${k.episode}" "Episode ${k.episode}"
} else { } else {
k.title k.title
@ -199,9 +203,13 @@ class AnimePaheProvider : MainAPI() {
data.data.forEach { data.data.forEach {
episodes.add( episodes.add(
AnimeEpisode( AnimeEpisode(
"https://animepahe.com/api?m=links&id=${it.animeId}&session=${it.session}&p=kwik!!TRUE!!", "$mainUrl/api?m=links&id=${it.animeId}&session=${it.session}&p=kwik!!TRUE!!",
getEpisodeTitle(it), getEpisodeTitle(it),
if (it.snapshot.length == 0) {null} else {it.snapshot}, if (it.snapshot.length == 0) {
null
} else {
it.snapshot
},
it.createdAt it.createdAt
) )
) )
@ -212,7 +220,7 @@ class AnimePaheProvider : MainAPI() {
if (ep <= total) { if (ep <= total) {
episodes.add( episodes.add(
AnimeEpisode( AnimeEpisode(
"https://animepahe.com/api?m=release&id=${id}&sort=episode_asc&page=${page + 1}&ep=${ep}!!FALSE!!" "$mainUrl/api?m=release&id=${id}&sort=episode_asc&page=${page + 1}&ep=${ep}!!FALSE!!"
) )
) )
++ep ++ep
@ -222,7 +230,7 @@ class AnimePaheProvider : MainAPI() {
} }
return episodes return episodes
} catch (e: Exception) { } catch (e: Exception) {
return ArrayList<AnimeEpisode>() return ArrayList()
} }
} }
@ -235,7 +243,7 @@ class AnimePaheProvider : MainAPI() {
val japTitle = doc.selectFirst("h2.japanese")?.text() val japTitle = doc.selectFirst("h2.japanese")?.text()
val poster = doc.selectFirst(".anime-poster a").attr("href") val poster = doc.selectFirst(".anime-poster a").attr("href")
val TvType = doc.selectFirst("""a[href*="/anime/type/"]""")?.text() val tvType = doc.selectFirst("""a[href*="/anime/type/"]""")?.text()
val trailer: String? = if (html.contains("https://www.youtube.com/watch")) { val trailer: String? = if (html.contains("https://www.youtube.com/watch")) {
YOUTUBE_VIDEO_LINK.find(html)?.destructured?.component1() YOUTUBE_VIDEO_LINK.find(html)?.destructured?.component1()
@ -243,9 +251,11 @@ class AnimePaheProvider : MainAPI() {
null null
} }
val episodes = generateListOfEpisodes(url) ?: ArrayList<AnimeEpisode>() val episodes = generateListOfEpisodes(url)
val year = """<strong>Aired:<\/strong>[^,]*, (\d+)""".toRegex().find(html)!!.destructured?.component1()?.toIntOrNull() val year = """<strong>Aired:</strong>[^,]*, (\d+)""".toRegex().find(html)!!.destructured.component1()
val status = when ("""<strong>Status:<\/strong>[^a]*a href=[\"']\/anime\/(.*?)[\"']""".toRegex().find(html)!!.destructured?.component1().toString()) { .toIntOrNull()
val status = when ("""<strong>Status:</strong>[^a]*a href=["']/anime/(.*?)["']""".toRegex()
.find(html)!!.destructured.component1()) {
"airing" -> ShowStatus.Ongoing "airing" -> ShowStatus.Ongoing
"completed" -> ShowStatus.Completed "completed" -> ShowStatus.Completed
else -> null else -> null
@ -259,9 +269,9 @@ class AnimePaheProvider : MainAPI() {
val split = aTag.attr("href").split("/") val split = aTag.attr("href").split("/")
if (aTag.attr("href").contains("anilist.co")) { if (aTag.attr("href").contains("anilist.co")) {
anilistId = split[split.size-1].toIntOrNull() anilistId = split[split.size - 1].toIntOrNull()
} else if (aTag.attr("href").contains("myanimelist.net")) { } else if (aTag.attr("href").contains("myanimelist.net")) {
malId = split[split.size-1].toIntOrNull() malId = split[split.size - 1].toIntOrNull()
} }
} }
@ -271,7 +281,7 @@ class AnimePaheProvider : MainAPI() {
title.toString(), title.toString(),
url, url,
this.name, this.name,
getType(TvType.toString()), getType(tvType.toString()),
poster, poster,
year, year,
null, null,
@ -280,7 +290,9 @@ class AnimePaheProvider : MainAPI() {
synopsis, synopsis,
if (!doc.select(".anime-genre > ul a").isEmpty()) { if (!doc.select(".anime-genre > ul a").isEmpty()) {
ArrayList(doc.select(".anime-genre > ul a").map { it.text().toString() }) ArrayList(doc.select(".anime-genre > ul a").map { it.text().toString() })
} else { null }, } else {
null
},
ArrayList(), ArrayList(),
malId, malId,
anilistId, anilistId,
@ -301,21 +313,25 @@ class AnimePaheProvider : MainAPI() {
for (string in cookie.split("; ")) { for (string in cookie.split("; ")) {
val split = string.split("=").toMutableList() val split = string.split("=").toMutableList()
val name = split.removeFirst().trim() val name = split.removeFirst().trim()
val value = if (split.size == 0) {"true"} else {split.joinToString("=")} val value = if (split.size == 0) {
"true"
} else {
split.joinToString("=")
}
cookies[name] = value cookies[name] = value
} }
return cookies.toMap() return cookies.toMap()
} }
private fun getString(content: String, s1: Int, s2:Int): String { private fun getString(content: String, s1: Int, s2: Int): String {
val characterMap: String = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"; val characterMap = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
val slice2 = characterMap.slice(0..s2-1) val slice2 = characterMap.slice(0 until s2)
var acc: Long = 0 var acc: Long = 0
for ((n, i) in content.reversed().withIndex()) { for ((n, i) in content.reversed().withIndex()) {
acc += (when(isNumber("$i")) { acc += (when (isNumber("$i")) {
true -> "$i".toLong() true -> "$i".toLong()
false -> "0".toLong() false -> "0".toLong()
}) * Math.pow(s1.toDouble(), n.toDouble()).toInt() }) * Math.pow(s1.toDouble(), n.toDouble()).toInt()
@ -335,7 +351,6 @@ class AnimePaheProvider : MainAPI() {
} }
private fun decrypt(fullString: String, key: String, v1: Int, v2: Int): String { private fun decrypt(fullString: String, key: String, v1: Int, v2: Int): String {
var r = "" var r = ""
var i = 0 var i = 0
@ -363,7 +378,7 @@ class AnimePaheProvider : MainAPI() {
val newList = ArrayList<Pair<Pair<Int, Int>, Pair<Int, Int>>>() val newList = ArrayList<Pair<Pair<Int, Int>, Pair<Int, Int>>>()
while (allItems.size > 1) { while (allItems.size > 1) {
newList.add(Pair<Pair<Int, Int>, Pair<Int, Int>>(allItems[0], allItems[1])) newList.add(Pair(allItems[0], allItems[1]))
allItems.removeAt(0) allItems.removeAt(0)
allItems.removeAt(0) allItems.removeAt(0)
} }
@ -371,7 +386,8 @@ class AnimePaheProvider : MainAPI() {
} }
private fun decodeAdfly(codedKey: String): String { private fun decodeAdfly(codedKey: String): String {
var r = ""; var j = "" var r = ""
var j = ""
for ((n, l) in codedKey.withIndex()) { for ((n, l) in codedKey.withIndex()) {
if (n % 2 != 0) { if (n % 2 != 0) {
@ -385,7 +401,7 @@ class AnimePaheProvider : MainAPI() {
val numbers = sequence { val numbers = sequence {
for ((i, n) in encodedUri.withIndex()) { for ((i, n) in encodedUri.withIndex()) {
if (isNumber(n)) { if (isNumber(n)) {
yield(Pair<Int, Int>(i, n.toInt())) yield(Pair(i, n.toInt()))
} }
} }
} }
@ -398,21 +414,20 @@ class AnimePaheProvider : MainAPI() {
} }
var returnValue = String(encodedUri.joinToString("").toByteArray(), Charsets.UTF_8) var returnValue = String(encodedUri.joinToString("").toByteArray(), Charsets.UTF_8)
returnValue = String(android.util.Base64.decode(returnValue, android.util.Base64.DEFAULT), Charsets.ISO_8859_1) returnValue = String(android.util.Base64.decode(returnValue, android.util.Base64.DEFAULT), Charsets.ISO_8859_1)
return returnValue.slice(16..returnValue.length-17) return returnValue.slice(16..returnValue.length - 17)
} }
private data class VideoQuality ( private data class VideoQuality(
@JsonProperty("id") val id : Int?, @JsonProperty("id") val id: Int?,
@JsonProperty("audio") val audio : String?, @JsonProperty("audio") val audio: String?,
@JsonProperty("kwik") val kwik : String?, @JsonProperty("kwik") val kwik: String?,
@JsonProperty("kwik_adfly") val kwikAdfly : String @JsonProperty("kwik_adfly") val kwikAdfly: String
) )
private data class AnimePaheEpisodeLoadLinks ( private data class AnimePaheEpisodeLoadLinks(
@JsonProperty("data") val data : List<Map<String, VideoQuality>> @JsonProperty("data") val data: List<Map<String, VideoQuality>>
) )
private fun bypassAdfly(adflyUri: String): String { private fun bypassAdfly(adflyUri: String): String {
if (!generateSession()) { if (!generateSession()) {
return bypassAdfly(adflyUri) return bypassAdfly(adflyUri)
@ -423,7 +438,11 @@ class AnimePaheProvider : MainAPI() {
var tries = 0 var tries = 0
while (responseCode != 200 && tries < 20) { while (responseCode != 200 && tries < 20) {
adflyContent = khttp.get(khttp.get(adflyUri, cookies=cookies, allowRedirects = false).headers.getValue("location"), cookies=cookies, allowRedirects = false) adflyContent = khttp.get(
khttp.get(adflyUri, cookies = cookies, allowRedirects = false).headers.getValue("location"),
cookies = cookies,
allowRedirects = false
)
cookies.putAll(adflyContent.cookies.toMap()) cookies.putAll(adflyContent.cookies.toMap())
responseCode = adflyContent.statusCode responseCode = adflyContent.statusCode
++tries ++tries
@ -435,10 +454,11 @@ class AnimePaheProvider : MainAPI() {
} }
private fun getStreamUrlFromKwik(adflyUri: String): String { private fun getStreamUrlFromKwik(adflyUri: String): String {
val fContent = khttp.get(bypassAdfly(adflyUri), headers=mapOf("referer" to "https://kwik.cx/"), cookies=cookies) val fContent =
khttp.get(bypassAdfly(adflyUri), headers = mapOf("referer" to "https://kwik.cx/"), cookies = cookies)
cookies.putAll(fContent.cookies.toMap()) cookies.putAll(fContent.cookies.toMap())
val (fullString, key, v1, v2) = KWIK_PARAMS_RE.find(fContent.text.toString())!!.destructured val (fullString, key, v1, v2) = KWIK_PARAMS_RE.find(fContent.text)!!.destructured
val decrypted = decrypt(fullString, key, v1.toInt(), v2.toInt()) val decrypted = decrypt(fullString, key, v1.toInt(), v2.toInt())
val uri = KWIK_D_URL.find(decrypted)!!.destructured.component1() val uri = KWIK_D_URL.find(decrypted)!!.destructured.component1()
val tok = KWIK_D_TOKEN.find(decrypted)!!.destructured.component1() val tok = KWIK_D_TOKEN.find(decrypted)!!.destructured.component1()
@ -451,9 +471,9 @@ class AnimePaheProvider : MainAPI() {
content = khttp.post( content = khttp.post(
uri, uri,
allowRedirects = false, allowRedirects = false,
data=mapOf("_token" to tok), data = mapOf("_token" to tok),
headers=mapOf("referer" to fContent.url), headers = mapOf("referer" to fContent.url),
cookies=cookieStrToMap(fContent.headers.getValue("set-cookie").replace("path=/,", "")) cookies = cookieStrToMap(fContent.headers.getValue("set-cookie").replace("path=/,", ""))
) )
code = content.statusCode code = content.statusCode
++tries ++tries
@ -466,7 +486,7 @@ class AnimePaheProvider : MainAPI() {
private fun extractVideoLinks(episodeLink: String): List<ExtractorLink> { private fun extractVideoLinks(episodeLink: String): List<ExtractorLink> {
var link = episodeLink var link = episodeLink
val headers = mapOf("referer" to "https://animepahe.com/") val headers = mapOf("referer" to "$mainUrl/")
if (link.contains("!!TRUE!!")) { if (link.contains("!!TRUE!!")) {
link = link.replace("!!TRUE!!", "") link = link.replace("!!TRUE!!", "")
@ -485,7 +505,7 @@ class AnimePaheProvider : MainAPI() {
null null
} }
}).filterNotNull())[0] }).filterNotNull())[0]
link = "https://animepahe.com/api?m=links&id=${ep.animeId}&session=${ep.session}&p=kwik" link = "$mainUrl/api?m=links&id=${ep.animeId}&session=${ep.session}&p=kwik"
} }
val req = khttp.get(link, headers = headers) val req = khttp.get(link, headers = headers)
val data = mapper.readValue<AnimePaheEpisodeLoadLinks>(req.text) val data = mapper.readValue<AnimePaheEpisodeLoadLinks>(req.text)

View file

@ -48,11 +48,11 @@ class GogoanimeProvider : MainAPI() {
"dnt" to "1", "dnt" to "1",
"sec-ch-ua-mobile" to "?0", "sec-ch-ua-mobile" to "?0",
"user-agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36", "user-agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36",
"origin" to "https://gogoanime.vc", "origin" to mainUrl,
"sec-fetch-site" to "cross-site", "sec-fetch-site" to "cross-site",
"sec-fetch-mode" to "cors", "sec-fetch-mode" to "cors",
"sec-fetch-dest" to "empty", "sec-fetch-dest" to "empty",
"referer" to "https://gogoanime.vc/" "referer" to "$mainUrl/"
) )
val parseRegex = Regex("""<li>\s*\n.*\n.*<a\s*href=["'](.*?-episode-(\d+))["']\s*title=["'](.*?)["']>\n.*?img src="(.*?)"""") val parseRegex = Regex("""<li>\s*\n.*\n.*<a\s*href=["'](.*?-episode-(\d+))["']\s*title=["'](.*?)["']>\n.*?img src="(.*?)"""")
@ -179,7 +179,7 @@ class GogoanimeProvider : MainAPI() {
title, title,
link, link,
this.name, this.name,
GogoanimeProvider.getType(type.toString()), getType(type.toString()),
poster, poster,
year, year,
null, null,

View file

@ -1,315 +0,0 @@
package com.lagradost.cloudstream3.animeproviders
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.extractors.Vidstream
import java.net.URLEncoder
import java.util.*
import kotlin.collections.ArrayList
const val SHIRO_TIMEOUT_TIME = 60.0
class ShiroProvider : MainAPI() {
companion object {
var token: String? = null
fun getType(t: String?): TvType {
return when (t) {
"TV" -> TvType.Anime
"OVA" -> TvType.ONA
"movie" -> TvType.Movie
else -> TvType.Anime
}
}
}
private fun autoLoadToken(): Boolean {
if (token != null) return true
return loadToken()
}
private fun loadToken(): Boolean {
return try {
val response = khttp.get(mainUrl, headers = baseHeader)
val jsMatch = Regex("""src="(/static/js/main.*?)"""").find(response.text)
val (destructed) = jsMatch!!.destructured
val jsLocation = "$mainUrl$destructed"
val js = khttp.get(jsLocation, headers = baseHeader)
val tokenMatch = Regex("""token:"(.*?)"""").find(js.text)
token = (tokenMatch!!.destructured).component1()
token != null
} catch (e: Exception) {
false
}
}
override val mainUrl: String
get() = "https://shiro.is"
override val name: String
get() = "Shiro"
override val hasQuickSearch: Boolean
get() = true
override val hasMainPage: Boolean
get() = true
data class ShiroSearchResponseShow(
@JsonProperty("image") val image: String,
@JsonProperty("_id") val _id: String,
@JsonProperty("slug") val slug: String,
@JsonProperty("name") val name: String,
@JsonProperty("episodeCount") val episodeCount: String?,
@JsonProperty("language") val language: String?,
@JsonProperty("type") val type: String?,
@JsonProperty("year") val year: String?,
@JsonProperty("canonicalTitle") val canonicalTitle: String,
@JsonProperty("english") val english: String?,
)
data class ShiroSearchResponse(
@JsonProperty("data") val data: List<ShiroSearchResponseShow>,
@JsonProperty("status") val status: String,
)
data class ShiroFullSearchResponseCurrentPage(
@JsonProperty("items") val items: List<ShiroSearchResponseShow>,
)
data class ShiroFullSearchResponseNavItems(
@JsonProperty("currentPage") val currentPage: ShiroFullSearchResponseCurrentPage,
)
data class ShiroFullSearchResponseNav(
@JsonProperty("nav") val nav: ShiroFullSearchResponseNavItems,
)
data class ShiroFullSearchResponse(
@JsonProperty("data") val data: ShiroFullSearchResponseNav,
@JsonProperty("status") val status: String,
)
data class ShiroVideo(
@JsonProperty("video_id") val video_id: String,
@JsonProperty("host") val host: String,
)
data class ShiroEpisodes(
@JsonProperty("anime") val anime: AnimePageData?,
@JsonProperty("anime_slug") val anime_slug: String,
@JsonProperty("create") val create: String,
@JsonProperty("dayOfTheWeek") val dayOfTheWeek: String,
@JsonProperty("episode_number") val episode_number: Int,
@JsonProperty("slug") val slug: String,
@JsonProperty("update") val update: String,
@JsonProperty("_id") val _id: String,
@JsonProperty("videos") val videos: List<ShiroVideo>,
)
data class AnimePageData(
@JsonProperty("banner") val banner: String?,
@JsonProperty("canonicalTitle") val canonicalTitle: String?,
@JsonProperty("episodeCount") val episodeCount: String,
@JsonProperty("genres") val genres: List<String>?,
@JsonProperty("image") val image: String,
@JsonProperty("japanese") val japanese: String?,
@JsonProperty("english") val english: String?,
@JsonProperty("language") val language: String,
@JsonProperty("name") val name: String,
@JsonProperty("slug") val slug: String,
@JsonProperty("synopsis") val synopsis: String,
@JsonProperty("type") val type: String?,
@JsonProperty("views") val views: Int?,
@JsonProperty("year") val year: String?,
@JsonProperty("_id") val _id: String,
@JsonProperty("episodes") var episodes: List<ShiroEpisodes>?,
@JsonProperty("synonyms") var synonyms: List<String>?,
@JsonProperty("status") val status: String?,
@JsonProperty("schedule") val schedule: String?,
)
data class AnimePage(
@JsonProperty("data") val data: AnimePageData,
@JsonProperty("status") val status: String,
)
data class ShiroHomePageData(
@JsonProperty("trending_animes") val trending_animes: List<AnimePageData>,
@JsonProperty("ongoing_animes") val ongoing_animes: List<AnimePageData>,
@JsonProperty("latest_animes") val latest_animes: List<AnimePageData>,
@JsonProperty("latest_episodes") val latest_episodes: List<ShiroEpisodes>,
)
data class ShiroHomePage(
@JsonProperty("status") val status: String,
@JsonProperty("data") val data: ShiroHomePageData,
@JsonProperty("random") var random: AnimePage?,
)
private fun toHomePageList(list: List<AnimePageData>, name: String): HomePageList {
return HomePageList(name, list.map { data ->
val type = getType(data.type)
val isDubbed =
data.language == "dubbed"
val set: EnumSet<DubStatus> =
EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
val episodeCount = data.episodeCount.toIntOrNull()
return@map AnimeSearchResponse(
data.name.replace("Dubbed", ""), // i.english ?: i.canonicalTitle,
"$mainUrl/anime/${data.slug}",
this.name,
type,
"https://cdn.shiro.is/${data.image}",
data.year?.toIntOrNull(),
data.canonicalTitle,
set,
if (isDubbed) episodeCount else null,
if (!isDubbed) episodeCount else null,
)
}.toList())
}
private fun turnSearchIntoResponse(data: ShiroSearchResponseShow): AnimeSearchResponse {
val type = getType(data.type)
val isDubbed =
if (data.language != null)
data.language == "dubbed"
else
data.slug.contains("dubbed")
val set: EnumSet<DubStatus> =
EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
val episodeCount = data.episodeCount?.toIntOrNull()
return AnimeSearchResponse(
data.name.replace("Dubbed", ""), // i.english ?: i.canonicalTitle,
"$mainUrl/anime/${data.slug}",
this.name,
type,
"https://cdn.shiro.is/${data.image}",
data.year?.toIntOrNull(),
data.canonicalTitle,
set,
if (isDubbed) episodeCount else null,
if (!isDubbed) episodeCount else null,
)
}
override fun getMainPage(): HomePageResponse? {
if (!autoLoadToken()) return null
val url = "https://tapi.shiro.is/latest?token=$token"
val response = khttp.get(url, timeout = SHIRO_TIMEOUT_TIME)
val res = response.text.let { mapper.readValue<ShiroHomePage>(it) }
val d = res.data
return HomePageResponse(
listOf(
toHomePageList(d.trending_animes, "Trending"),
toHomePageList(d.ongoing_animes, "Ongoing"),
toHomePageList(d.latest_animes, "Latest")
)
)
}
override fun quickSearch(query: String): ArrayList<SearchResponse>? {
if (!autoLoadToken()) return null
val returnValue: ArrayList<SearchResponse> = ArrayList()
val response = khttp.get(
"https://tapi.shiro.is/anime/auto-complete/${
URLEncoder.encode(
query,
"UTF-8"
)
}?token=$token".replace("+", "%20")
)
if (response.text == "{\"status\":\"Found\",\"data\":[]}") return returnValue // OR ELSE WILL CAUSE WEIRD ERROR
val mapped = response.let { mapper.readValue<ShiroSearchResponse>(it.text) }
for (i in mapped.data) {
returnValue.add(turnSearchIntoResponse(i))
}
return returnValue
}
override fun search(query: String): ArrayList<SearchResponse>? {
if (!autoLoadToken()) return null
val returnValue: ArrayList<SearchResponse> = ArrayList()
val response = khttp.get(
"https://tapi.shiro.is/advanced?search=${
URLEncoder.encode(
query,
"UTF-8"
)
}&token=$token".replace("+", "%20")
)
if (response.text == "{\"status\":\"Found\",\"data\":[]}") return returnValue // OR ELSE WILL CAUSE WEIRD ERROR
val mapped = response.let { mapper.readValue<ShiroFullSearchResponse>(it.text) }
for (i in mapped.data.nav.currentPage.items) {
returnValue.add(turnSearchIntoResponse(i))
}
return returnValue
}
override fun load(url: String): LoadResponse? {
if (!autoLoadToken()) return null
val slug = url.replace("$mainUrl/anime/", "").replace("$mainUrl/", "")
val rurl = "https://tapi.shiro.is/anime/slug/${slug}?token=${token}"
val response = khttp.get(rurl, timeout = 120.0)
val mapped = response.let { mapper.readValue<AnimePage>(it.text) }
val data = mapped.data
val isDubbed = data.language == "dubbed"
val episodes =
ArrayList<AnimeEpisode>(
data.episodes?.distinctBy { it.episode_number }?.sortedBy { it.episode_number }
?.filter { it.videos.isNotEmpty() }
?.map { AnimeEpisode(it.videos.first().video_id) }
?: ArrayList<AnimeEpisode>())
val status = when (data.status) {
"current" -> ShowStatus.Ongoing
"finished" -> ShowStatus.Completed
else -> null
}
return AnimeLoadResponse(
data.english,
data.japanese,
data.name.replace("Dubbed", ""),//data.canonicalTitle ?: data.name.replace("Dubbed", ""),
"$mainUrl/anime/${url}",
this.name,
getType(data.type ?: ""),
"https://cdn.shiro.is/${data.image}",
data.year?.toIntOrNull(),
if (isDubbed) episodes else null,
if (!isDubbed) episodes else null,
status,
data.synopsis,
ArrayList(data.genres ?: ArrayList()),
ArrayList(data.synonyms ?: ArrayList()),
null,
null,
)
}
override fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
return Vidstream().getUrl(data, isCasting) {
callback.invoke(it)
}
}
}

View file

@ -217,7 +217,7 @@ class TenshiProvider : MainAPI() {
canonicalTitle, canonicalTitle,
url, url,
this.name, this.name,
TenshiProvider.getType(type ?: ""), getType(type ?: ""),
poster, poster,
year.toIntOrNull(), year.toIntOrNull(),
null, null,

View file

@ -23,7 +23,7 @@ class WatchCartoonOnlineProvider : MainAPI() {
TvType.Anime, TvType.Anime,
) )
override fun search(query: String): ArrayList<SearchResponse>? { override fun search(query: String): List<SearchResponse> {
val url = "https://www.wcostream.com/search" val url = "https://www.wcostream.com/search"
val response = val response =
@ -69,7 +69,7 @@ class WatchCartoonOnlineProvider : MainAPI() {
return returnValue return returnValue
} }
override fun load(url: String): LoadResponse? { override fun load(url: String): LoadResponse {
val response = khttp.get(url) val response = khttp.get(url)
val document = Jsoup.parse(response.text) val document = Jsoup.parse(response.text)

View file

@ -34,7 +34,7 @@ class WcoProvider : MainAPI() {
TvType.ONA TvType.ONA
) )
override fun getMainPage(): HomePageResponse? { override fun getMainPage(): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair("$mainUrl/ajax/list/recently_updated?type=tv", "Recently Updated Anime"), Pair("$mainUrl/ajax/list/recently_updated?type=tv", "Recently Updated Anime"),
Pair("$mainUrl/ajax/list/recently_updated?type=movie", "Recently Updated Movies"), Pair("$mainUrl/ajax/list/recently_updated?type=movie", "Recently Updated Movies"),
@ -114,7 +114,7 @@ class WcoProvider : MainAPI() {
return returnValue return returnValue
} }
override fun search(query: String): ArrayList<SearchResponse> { override fun search(query: String): List<SearchResponse> {
val url = "$mainUrl/search" val url = "$mainUrl/search"
val response = khttp.get(url, params = mapOf("keyword" to query)) val response = khttp.get(url, params = mapOf("keyword" to query))
var document = Jsoup.parse(response.text) var document = Jsoup.parse(response.text)
@ -133,7 +133,7 @@ class WcoProvider : MainAPI() {
return returnValue return returnValue
} }
override fun quickSearch(query: String): ArrayList<SearchResponse> { override fun quickSearch(query: String): List<SearchResponse> {
val returnValue: ArrayList<SearchResponse> = ArrayList() val returnValue: ArrayList<SearchResponse> = ArrayList()
val response = khttp.post( val response = khttp.post(

View file

@ -18,7 +18,7 @@ class HDMProvider : MainAPI() {
TvType.Movie, TvType.Movie,
) )
override fun search(query: String): ArrayList<SearchResponse> { override fun search(query: String): List<SearchResponse> {
val url = "$mainUrl/search/$query" val url = "$mainUrl/search/$query"
val response = khttp.get(url) val response = khttp.get(url)
val document = Jsoup.parse(response.text) val document = Jsoup.parse(response.text)

View file

@ -69,7 +69,7 @@ class LookMovieProvider : MainAPI() {
@JsonProperty("season") var season: String, @JsonProperty("season") var season: String,
) )
override fun quickSearch(query: String): ArrayList<SearchResponse> { override fun quickSearch(query: String): List<SearchResponse> {
val movieUrl = "$mainUrl/api/v1/movies/search/?q=$query" val movieUrl = "$mainUrl/api/v1/movies/search/?q=$query"
val movieResponse = khttp.get(movieUrl) val movieResponse = khttp.get(movieUrl)
val movies = mapper.readValue<LookMovieSearchResultRoot>(movieResponse.text).result val movies = mapper.readValue<LookMovieSearchResultRoot>(movieResponse.text).result
@ -114,7 +114,7 @@ class LookMovieProvider : MainAPI() {
return returnValue return returnValue
} }
override fun search(query: String): ArrayList<SearchResponse> { override fun search(query: String): List<SearchResponse> {
fun search(query: String, isMovie: Boolean): ArrayList<SearchResponse> { fun search(query: String, isMovie: Boolean): ArrayList<SearchResponse> {
val url = "$mainUrl/${if (isMovie) "movies" else "shows"}/search/?q=$query" val url = "$mainUrl/${if (isMovie) "movies" else "shows"}/search/?q=$query"
val response = khttp.get(url) val response = khttp.get(url)
@ -248,7 +248,7 @@ class LookMovieProvider : MainAPI() {
val accessToken = root.data?.accessToken ?: return null val accessToken = root.data?.accessToken ?: return null
val window = val window =
"window\\[\\'show_storage\\'\\] =((.|\\n)*?\\<)".toRegex().find(response.text)?.groupValues?.get(1) "window\\['show_storage'] =((.|\\n)*?<)".toRegex().find(response.text)?.groupValues?.get(1)
?: return null ?: return null
// val id = "id_show:(.*?),".toRegex().find(response.text)?.groupValues?.get(1) ?: return null // val id = "id_show:(.*?),".toRegex().find(response.text)?.groupValues?.get(1) ?: return null
val season = "seasons:.*\\[((.|\\n)*?)]".toRegex().find(window)?.groupValues?.get(1) ?: return null val season = "seasons:.*\\[((.|\\n)*?)]".toRegex().find(window)?.groupValues?.get(1) ?: return null

View file

@ -31,11 +31,11 @@ class MeloMovieProvider : MainAPI() {
data class MeloMovieLink(val name: String, val link: String) data class MeloMovieLink(val name: String, val link: String)
override fun quickSearch(query: String): ArrayList<SearchResponse> { override fun quickSearch(query: String): List<SearchResponse> {
return search(query) return search(query)
} }
override fun search(query: String): ArrayList<SearchResponse> { override fun search(query: String): List<SearchResponse> {
val url = "$mainUrl/movie/search/?name=$query" val url = "$mainUrl/movie/search/?name=$query"
val returnValue: ArrayList<SearchResponse> = ArrayList() val returnValue: ArrayList<SearchResponse> = ArrayList()
val response = khttp.get(url) val response = khttp.get(url)

View file

@ -71,7 +71,7 @@ class TrailersToProvider : MainAPI() {
//section.section > div.container > div.owl-carousel //section.section > div.container > div.owl-carousel
} }
override fun quickSearch(query: String): ArrayList<SearchResponse> { override fun quickSearch(query: String): List<SearchResponse> {
val url = "$mainUrl/en/quick-search?q=$query" val url = "$mainUrl/en/quick-search?q=$query"
val response = khttp.get(url) val response = khttp.get(url)
val document = Jsoup.parse(response.text) val document = Jsoup.parse(response.text)
@ -99,7 +99,7 @@ class TrailersToProvider : MainAPI() {
return returnValue return returnValue
} }
override fun search(query: String): ArrayList<SearchResponse> { override fun search(query: String): List<SearchResponse> {
val url = "$mainUrl/en/popular/movies-tvshows-collections?q=$query" val url = "$mainUrl/en/popular/movies-tvshows-collections?q=$query"
val response = khttp.get(url) val response = khttp.get(url)
val document = Jsoup.parse(response.text) val document = Jsoup.parse(response.text)
@ -180,7 +180,7 @@ class TrailersToProvider : MainAPI() {
} else if (url.contains("/episode/")) { } else if (url.contains("/episode/")) {
val response = khttp.get(url) val response = khttp.get(url)
val document = Jsoup.parse(response.text) val document = Jsoup.parse(response.text)
val qSub = document.select("subtitle-content") //val qSub = document.select("subtitle-content")
val subUrl = document.select("subtitle-content")?.attr("data-url") ?: "" val subUrl = document.select("subtitle-content")?.attr("data-url") ?: ""
val subData = fixUrl(document.selectFirst("content").attr("data-url") ?: return false) val subData = fixUrl(document.selectFirst("content").attr("data-url") ?: return false)

View file

@ -19,7 +19,7 @@ class VMoveeProvider : MainAPI() {
TvType.Movie, TvType.Movie,
) )
override fun search(query: String): ArrayList<SearchResponse>? { override fun search(query: String): List<SearchResponse> {
val url = "$mainUrl/?s=$query" val url = "$mainUrl/?s=$query"
val response = khttp.get(url) val response = khttp.get(url)
val document = Jsoup.parse(response.text) val document = Jsoup.parse(response.text)
@ -110,7 +110,7 @@ class VMoveeProvider : MainAPI() {
return super.loadLinks(data, isCasting, subtitleCallback, callback) return super.loadLinks(data, isCasting, subtitleCallback, callback)
} }
override fun load(url: String): LoadResponse? { override fun load(url: String): LoadResponse {
val response = khttp.get(url) val response = khttp.get(url)
val document = Jsoup.parse(response.text) val document = Jsoup.parse(response.text)

View file

@ -56,7 +56,7 @@ class DownloadChildAdapter(
override fun onViewRecycled(holder: RecyclerView.ViewHolder) { override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
if (holder is DownloadButtonViewHolder) { if (holder is DownloadButtonViewHolder) {
holder.downloadButton.dispose() holder.downloadButton.dispose()
mBoundViewHolders.remove(holder); mBoundViewHolders.remove(holder)
} }
} }

View file

@ -9,10 +9,8 @@ import android.widget.TextView
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.widget.ContentLoadingProgressBar import androidx.core.widget.ContentLoadingProgressBar
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.IDisposable import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import kotlinx.android.synthetic.main.download_header_episode.view.* import kotlinx.android.synthetic.main.download_header_episode.view.*
import java.util.* import java.util.*
@ -54,7 +52,7 @@ class DownloadHeaderAdapter(
override fun onViewRecycled(holder: RecyclerView.ViewHolder) { override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
if (holder is DownloadButtonViewHolder) { if (holder is DownloadButtonViewHolder) {
holder.downloadButton.dispose() holder.downloadButton.dispose()
mBoundViewHolders.remove(holder); mBoundViewHolders.remove(holder)
} }
} }
@ -93,7 +91,7 @@ class DownloadHeaderAdapter(
) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder { ) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder {
override var downloadButton = EasyDownloadButton() override var downloadButton = EasyDownloadButton()
private val poster: ImageView = itemView.download_header_poster private val poster: ImageView? = itemView.download_header_poster
private val title: TextView = itemView.download_header_title private val title: TextView = itemView.download_header_title
private val extraInfo: TextView = itemView.download_header_info private val extraInfo: TextView = itemView.download_header_info
private val holder: CardView = itemView.episode_holder private val holder: CardView = itemView.episode_holder
@ -107,17 +105,8 @@ class DownloadHeaderAdapter(
fun bind(card: VisualDownloadHeaderCached) { fun bind(card: VisualDownloadHeaderCached) {
localCard = card localCard = card
val d = card.data val d = card.data
if (d.poster != null) {
val glideUrl = poster?.setImage(d.poster)
GlideUrl(d.poster)
poster.context.let {
Glide.with(it)
.load(glideUrl)
.into(poster)
}
}
title.text = d.name title.text = d.name
val mbString = "%.1f".format(card.totalBytes / 1000000f) val mbString = "%.1f".format(card.totalBytes / 1000000f)

View file

@ -38,8 +38,8 @@ class EasyDownloadButton : IDisposable {
clickCallback: (DownloadClickEvent) -> Unit, clickCallback: (DownloadClickEvent) -> Unit,
) { ) {
setUpDownloadButton(setupCurrentBytes, setupTotalBytes, progressBar, textView, data, downloadButton, { setUpDownloadButton(setupCurrentBytes, setupTotalBytes, progressBar, textView, data, downloadButton, {
downloadButton?.setIconResource(it.first) downloadButton.setIconResource(it.first)
downloadButton?.text = it.second downloadButton.text = it.second
}, clickCallback) }, clickCallback)
} }
@ -53,7 +53,7 @@ class EasyDownloadButton : IDisposable {
clickCallback: (DownloadClickEvent) -> Unit, clickCallback: (DownloadClickEvent) -> Unit,
) { ) {
setUpDownloadButton(setupCurrentBytes, setupTotalBytes, progressBar, textView, data, downloadImage, { setUpDownloadButton(setupCurrentBytes, setupTotalBytes, progressBar, textView, data, downloadImage, {
downloadImage?.setImageResource(it.first) downloadImage.setImageResource(it.first)
}, clickCallback) }, clickCallback)
} }
@ -98,20 +98,20 @@ class EasyDownloadButton : IDisposable {
if (currentBytes == 0L) { if (currentBytes == 0L) {
changeDownloadImage(VideoDownloadManager.DownloadType.IsStopped) changeDownloadImage(VideoDownloadManager.DownloadType.IsStopped)
textView?.visibility = View.GONE textView?.visibility = View.GONE
progressBar?.visibility = View.GONE progressBar.visibility = View.GONE
} else { } else {
if (lastState == VideoDownloadManager.DownloadType.IsStopped) { if (lastState == VideoDownloadManager.DownloadType.IsStopped) {
changeDownloadImage(VideoDownloadManager.getDownloadState(data.id)) changeDownloadImage(VideoDownloadManager.getDownloadState(data.id))
} }
textView?.visibility = View.VISIBLE textView?.visibility = View.VISIBLE
progressBar?.visibility = View.VISIBLE progressBar.visibility = View.VISIBLE
val currentMbString = "%.1f".format(setCurrentBytes / 1000000f) val currentMbString = "%.1f".format(setCurrentBytes / 1000000f)
val totalMbString = "%.1f".format(setTotalBytes / 1000000f) val totalMbString = "%.1f".format(setTotalBytes / 1000000f)
textView?.text = textView?.text =
"${currentMbString}MB / ${totalMbString}MB" "${currentMbString}MB / ${totalMbString}MB"
progressBar?.let { bar -> progressBar.let { bar ->
bar.max = (setTotalBytes / 1000).toInt() bar.max = (setTotalBytes / 1000).toInt()
if (animate) { if (animate) {

View file

@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.home_result_grid.view.* import kotlinx.android.synthetic.main.home_result_grid.view.*
class HomeChildItemAdapter( class HomeChildItemAdapter(
@ -72,16 +73,7 @@ class HomeChildItemAdapter(
cardText.text = card.name cardText.text = card.name
//imageTextProvider.text = card.apiName //imageTextProvider.text = card.apiName
if (!card.posterUrl.isNullOrEmpty()) { cardView.setImage(card.posterUrl)
val glideUrl =
GlideUrl(card.posterUrl)
Glide.with(cardView.context)
.load(glideUrl)
.into(cardView)
}
bg.setOnClickListener { bg.setOnClickListener {
clickCallback.invoke(SearchClickCallback(SEARCH_ACTION_LOAD, it, card)) clickCallback.invoke(SearchClickCallback(SEARCH_ACTION_LOAD, it, card))

View file

@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.ui.home
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
@ -16,15 +15,10 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.MainActivity.Companion.backEvent import com.lagradost.cloudstream3.MainActivity.Companion.backEvent
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getGridIsCompact
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.AutofitRecyclerView import com.lagradost.cloudstream3.ui.AutofitRecyclerView
@ -36,12 +30,15 @@ import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.HOMEPAGE_API import com.lagradost.cloudstream3.utils.HOMEPAGE_API
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getGridIsCompact
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.*
const val HOME_BOOKMARK_VALUE = "home_bookmarked_last" const val HOME_BOOKMARK_VALUE = "home_bookmarked_last"
@ -148,18 +145,7 @@ class HomeFragment : Fragment() {
home_main_text.text = random.name + if (random is AnimeSearchResponse) { home_main_text.text = random.name + if (random is AnimeSearchResponse) {
random.dubStatus?.joinToString(prefix = "", separator = " | ") { it.name } random.dubStatus?.joinToString(prefix = "", separator = " | ") { it.name }
} else "" } else ""
val glideUrl = home_main_poster?.setImage(random.posterUrl)
GlideUrl(random.posterUrl)
requireContext().let {
Glide.with(it)
.load(glideUrl)
.into(home_main_poster)
/*
Glide.with(it)
.load(glideUrl)
.apply(RequestOptions.bitmapTransform(BlurTransformation(80, 3)))
.into(result_poster_blur)*/
}
toggleMainVisibility(true) toggleMainVisibility(true)
return random return random

View file

@ -545,17 +545,17 @@ class PlayerFragment : Fragment() {
val isAnime = val isAnime =
data.isAnimeBased()//(data is AnimeLoadResponse && (data.type == TvType.Anime || data.type == TvType.ONA)) data.isAnimeBased()//(data is AnimeLoadResponse && (data.type == TvType.Anime || data.type == TvType.ONA))
skip_op.setVis(isAnime && !nextEp) skip_op?.setVis(isAnime && !nextEp)
skip_episode.setVis((!isAnime || nextEp) && hasNext) skip_episode.setVis((!isAnime || nextEp) && hasNext)
} else { } else {
val isAnime = data.isAnimeBased() val isAnime = data.isAnimeBased()
if (isAnime) { if (isAnime) {
skip_op.setVis(true) skip_op?.setVis(true)
skip_episode.setVis(false) skip_episode.setVis(false)
} else { } else {
skip_episode.setVis(data.isEpisodeBased()) skip_episode.setVis(data.isEpisodeBased())
skip_op.setVis(false) skip_op?.setVis(false)
} }
} }
} }
@ -684,7 +684,7 @@ class PlayerFragment : Fragment() {
exo_progress.isClickable = isClick exo_progress.isClickable = isClick
//next_episode_btt.isClickable = isClick //next_episode_btt.isClickable = isClick
playback_speed_btt.isClickable = isClick playback_speed_btt.isClickable = isClick
skip_op.isClickable = isClick skip_op?.isClickable = isClick
skip_episode.isClickable = isClick skip_episode.isClickable = isClick
resize_player.isClickable = isClick resize_player.isClickable = isClick
exo_progress.isEnabled = isClick exo_progress.isEnabled = isClick
@ -1301,7 +1301,7 @@ class PlayerFragment : Fragment() {
resize_player.visibility = GONE resize_player.visibility = GONE
} }
skip_op.setOnClickListener { skip_op?.setOnClickListener {
skipOP() skipOP()
} }
@ -1787,7 +1787,7 @@ class PlayerFragment : Fragment() {
} }
override fun onPlayerError(error: ExoPlaybackException) { override fun onPlayerError(error: ExoPlaybackException) {
println("CURRENT URL: " + currentUrl?.url ?: uri) println("CURRENT URL: " + currentUrl?.url)
// Lets pray this doesn't spam Toasts :) // Lets pray this doesn't spam Toasts :)
when (error.type) { when (error.type) {
ExoPlaybackException.TYPE_SOURCE -> { ExoPlaybackException.TYPE_SOURCE -> {
@ -1854,7 +1854,9 @@ class PlayerFragment : Fragment() {
initPlayer(getCurrentUrl()) initPlayer(getCurrentUrl())
} }
} else { } else {
Toast.makeText(context, "No Links Found", Toast.LENGTH_SHORT).show() context?.let { ctx ->
Toast.makeText(ctx, "No Links Found", LENGTH_SHORT).show()
}
} }
} }
} }

View file

@ -1,7 +1,6 @@
package com.lagradost.cloudstream3.ui.result package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -11,13 +10,12 @@ import android.widget.Toast
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.core.widget.ContentLoadingProgressBar import androidx.core.widget.ContentLoadingProgressBar
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.android.synthetic.main.result_episode.view.episode_holder import kotlinx.android.synthetic.main.result_episode.view.episode_holder
@ -71,7 +69,7 @@ class EpisodeAdapter(
override fun onViewRecycled(holder: RecyclerView.ViewHolder) { override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
if (holder is DownloadButtonViewHolder) { if (holder is DownloadButtonViewHolder) {
holder.downloadButton.dispose() holder.downloadButton.dispose()
mBoundViewHolders.remove(holder); mBoundViewHolders.remove(holder)
} }
} }
@ -152,13 +150,7 @@ class EpisodeAdapter(
if (card.poster != null) { if (card.poster != null) {
episodePoster?.visibility = View.VISIBLE episodePoster?.visibility = View.VISIBLE
if (episodePoster != null) { episodePoster?.setImage(card.poster)
val glideUrl =
GlideUrl(card.poster)
Glide.with(episodePoster.context)
.load(glideUrl)
.into(episodePoster)
}
} else { } else {
episodePoster?.visibility = View.GONE episodePoster?.visibility = View.GONE
} }

View file

@ -61,6 +61,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getFolderName import com.lagradost.cloudstream3.utils.DataStore.getFolderName
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
@ -398,7 +399,7 @@ class ResultFragment : Fragment() {
fun startChromecast(startIndex: Int) { fun startChromecast(startIndex: Int) {
val eps = currentEpisodes ?: return val eps = currentEpisodes ?: return
context?.startCast( context?.startCast(
apiName ?: return, apiName,
currentIsMovie ?: return, currentIsMovie ?: return,
currentHeaderName, currentHeaderName,
currentPoster, currentPoster,
@ -850,19 +851,7 @@ class ResultFragment : Fragment() {
} }
if (d.posterUrl != null) { if (d.posterUrl != null) {
val glideUrl = result_poster?.setImage(d.posterUrl)
GlideUrl(d.posterUrl)
requireContext().let {
Glide.with(it)
.load(glideUrl)
.into(result_poster)
Glide.with(it)
.load(glideUrl)
.apply(bitmapTransform(BlurTransformation(80, 3)))
.into(result_poster_blur)
}
} }
if (d.plot != null) { if (d.plot != null) {

View file

@ -19,6 +19,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.getGridFormatId
import com.lagradost.cloudstream3.utils.UIHelper.getGridIsCompact import com.lagradost.cloudstream3.utils.UIHelper.getGridIsCompact
import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.ui.AutofitRecyclerView import com.lagradost.cloudstream3.ui.AutofitRecyclerView
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.search_result_compact.view.backgroundCard import kotlinx.android.synthetic.main.search_result_compact.view.backgroundCard
import kotlinx.android.synthetic.main.search_result_compact.view.imageText import kotlinx.android.synthetic.main.search_result_compact.view.imageText
import kotlinx.android.synthetic.main.search_result_compact.view.imageView import kotlinx.android.synthetic.main.search_result_compact.view.imageView
@ -104,15 +105,7 @@ class SearchAdapter(
cardText.text = card.name cardText.text = card.name
//imageTextProvider.text = card.apiName //imageTextProvider.text = card.apiName
if (!card.posterUrl.isNullOrEmpty()) { cardView.setImage(card.posterUrl)
val glideUrl =
GlideUrl(card.posterUrl)
Glide.with(cardView.context)
.load(glideUrl)
.into(cardView)
}
bg.setOnClickListener { bg.setOnClickListener {
clickCallback.invoke(SearchClickCallback(SEARCH_ACTION_LOAD, it, card)) clickCallback.invoke(SearchClickCallback(SEARCH_ACTION_LOAD, it, card))

View file

@ -171,11 +171,11 @@ class SearchFragment : Fragment() {
getString(if (isOn) R.string.search_provider_text_types else R.string.search_provider_text_providers) getString(if (isOn) R.string.search_provider_text_types else R.string.search_provider_text_providers)
if (isOn) { if (isOn) {
listView2?.visibility = View.VISIBLE listView2.visibility = View.VISIBLE
listView?.visibility = View.GONE listView.visibility = View.GONE
} else { } else {
listView?.visibility = View.VISIBLE listView.visibility = View.VISIBLE
listView2?.visibility = View.GONE listView2.visibility = View.GONE
} }
} }
@ -190,7 +190,7 @@ class SearchFragment : Fragment() {
listView.setOnItemClickListener { _, _, i, _ -> listView.setOnItemClickListener { _, _, i, _ ->
val types = HashSet<TvType>() val types = HashSet<TvType>()
for ((index, api) in apis.withIndex()) { for ((index, api) in apis.withIndex()) {
if (listView?.checkedItemPositions[index]) { if (listView.checkedItemPositions[index]) {
types.addAll(api.supportedTypes) types.addAll(api.supportedTypes)
} }
} }
@ -204,14 +204,14 @@ class SearchFragment : Fragment() {
var isSupported = false var isSupported = false
for ((typeIndex, type) in typeChoices.withIndex()) { for ((typeIndex, type) in typeChoices.withIndex()) {
if (listView2?.checkedItemPositions[typeIndex]) { if (listView2.checkedItemPositions[typeIndex]) {
if (api.supportedTypes.any { type.second.contains(it) }) { if (api.supportedTypes.any { type.second.contains(it) }) {
isSupported = true isSupported = true
} }
} }
} }
listView?.setItemChecked( listView.setItemChecked(
index, index,
isSupported isSupported
) )
@ -221,7 +221,7 @@ class SearchFragment : Fragment() {
} }
dialog.setOnDismissListener { dialog.setOnDismissListener {
context?.setKey(SEARCH_PROVIDER_TOGGLE, toggle.isChecked ?: true) context?.setKey(SEARCH_PROVIDER_TOGGLE, toggle.isChecked)
} }
applyButton.setOnClickListener { applyButton.setOnClickListener {
@ -229,7 +229,7 @@ class SearchFragment : Fragment() {
val activeTypes = HashSet<TvType>() val activeTypes = HashSet<TvType>()
for ((index, name) in typeChoices.withIndex()) { for ((index, name) in typeChoices.withIndex()) {
if (listView2?.checkedItemPositions[index]) { if (listView2.checkedItemPositions[index]) {
activeTypes.addAll(typeChoices[index].second) activeTypes.addAll(typeChoices[index].second)
} }
} }
@ -241,7 +241,7 @@ class SearchFragment : Fragment() {
val activeApis = HashSet<String>() val activeApis = HashSet<String>()
for ((index, name) in apiNames.withIndex()) { for ((index, name) in apiNames.withIndex()) {
if (listView?.checkedItemPositions[index]) { if (listView.checkedItemPositions[index]) {
activeApis.add(name) activeApis.add(name)
} }
} }

View file

@ -8,17 +8,16 @@ import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Color import android.graphics.Color
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build import android.os.Build
import android.view.Gravity import android.view.Gravity
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.RequiresApi
import androidx.appcompat.view.ContextThemeWrapper import androidx.appcompat.view.ContextThemeWrapper
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
@ -31,6 +30,8 @@ import androidx.core.graphics.red
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -83,6 +84,17 @@ object UIHelper {
return color return color
} }
fun ImageView?.setImage(url : String?) {
if(this == null || url.isNullOrBlank()) return
try {
Glide.with(this.context)
.load(GlideUrl(url))
.into(this)
} catch (e : Exception) {
e.printStackTrace()
}
}
fun adjustAlpha(@ColorInt color: Int, factor: Float): Int { fun adjustAlpha(@ColorInt color: Int, factor: Float): Int {
val alpha = (Color.alpha(color) * factor).roundToInt() val alpha = (Color.alpha(color) * factor).roundToInt()
val red = Color.red(color) val red = Color.red(color)
@ -243,6 +255,7 @@ object UIHelper {
return settingsManager?.getBoolean("pip_enabled", true) ?: true && isInPlayer return settingsManager?.getBoolean("pip_enabled", true) ?: true && isInPlayer
} }
@RequiresApi(Build.VERSION_CODES.O)
fun Context.hasPIPPermission(): Boolean { fun Context.hasPIPPermission(): Boolean {
val appOps = val appOps =
getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager

View file

@ -19,15 +19,14 @@ import androidx.core.net.toUri
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.services.VideoDownloadService import com.lagradost.cloudstream3.services.VideoDownloadService
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -37,7 +36,6 @@ import java.net.URL
import java.net.URLConnection import java.net.URLConnection
import java.util.* import java.util.*
const val DOWNLOAD_CHANNEL_ID = "cloudstream3.general" const val DOWNLOAD_CHANNEL_ID = "cloudstream3.general"
const val DOWNLOAD_CHANNEL_NAME = "Downloads" const val DOWNLOAD_CHANNEL_NAME = "Downloads"
const val DOWNLOAD_CHANNEL_DESCRIPT = "The download notification channel" const val DOWNLOAD_CHANNEL_DESCRIPT = "The download notification channel"
@ -180,18 +178,22 @@ object VideoDownloadManager {
private val cachedBitmaps = hashMapOf<String, Bitmap>() private val cachedBitmaps = hashMapOf<String, Bitmap>()
private fun Context.getImageBitmapFromUrl(url: String): Bitmap? { private fun Context.getImageBitmapFromUrl(url: String): Bitmap? {
if (cachedBitmaps.containsKey(url)) { try {
return cachedBitmaps[url] if (cachedBitmaps.containsKey(url)) {
} return cachedBitmaps[url]
}
val bitmap = Glide.with(this) val bitmap = Glide.with(this)
.asBitmap() .asBitmap()
.load(url).into(720, 720) .load(url).into(720, 720)
.get() .get()
if (bitmap != null) { if (bitmap != null) {
cachedBitmaps[url] = bitmap cachedBitmaps[url] = bitmap
}
return null
} catch (e : Exception) {
return null
} }
return null
} }
private fun createNotification( private fun createNotification(

View file

@ -8,11 +8,10 @@ import com.lagradost.cloudstream3.R
import kotlin.math.max import kotlin.math.max
class FlowLayout : ViewGroup { class FlowLayout : ViewGroup {
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)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val realWidth = MeasureSpec.getSize(widthMeasureSpec) val realWidth = MeasureSpec.getSize(widthMeasureSpec)
@ -92,7 +91,7 @@ class FlowLayout : ViewGroup {
spacing = 0 spacing = 0
} }
constructor(source: MarginLayoutParams?) : super(source) {} constructor(source: MarginLayoutParams?) : super(source)
internal constructor(source: ViewGroup.LayoutParams?) : super(source) {} internal constructor(source: ViewGroup.LayoutParams?) : super(source)
} }
} }