Merge remote-tracking branch 'origin/master'

This commit is contained in:
Blatzar 2022-07-30 17:44:24 +02:00
commit 59b5732925
97 changed files with 570 additions and 237 deletions

View file

@ -376,6 +376,32 @@ data class ProvidersInfoJson(
@JsonProperty("status") var status: Int, @JsonProperty("status") var status: Int,
) )
data class MainPageData(
val name: String,
val data: String,
)
/** return list of MainPageData with url to name, make for more readable code */
fun mainPageOf(vararg elements: Pair<String, String>): List<MainPageData> {
return elements.map { (url, name) -> MainPageData(name = name, data = url) }
}
fun newHomePageResponse(
name: String,
list: List<SearchResponse>,
hasNext: Boolean? = null
): HomePageResponse {
return HomePageResponse(
listOf(HomePageList(name, list)),
hasNext = hasNext ?: list.isNotEmpty()
)
}
fun newHomePageResponse(list: HomePageList, hasNext: Boolean? = null): HomePageResponse {
return HomePageResponse(listOf(list), hasNext = hasNext ?: list.list.isNotEmpty())
}
/**Every provider will **not** have try catch built in, so handle exceptions when calling these functions*/ /**Every provider will **not** have try catch built in, so handle exceptions when calling these functions*/
abstract class MainAPI { abstract class MainAPI {
companion object { companion object {
@ -431,8 +457,14 @@ abstract class MainAPI {
open val vpnStatus = VPNStatus.None open val vpnStatus = VPNStatus.None
open val providerType = ProviderType.DirectProvider open val providerType = ProviderType.DirectProvider
open val mainPage = listOf(MainPageData("", ""))
@WorkerThread @WorkerThread
open suspend fun getMainPage(): HomePageResponse? { open suspend fun getMainPage(
page: Int,
categoryName: String,
categoryData: String
): HomePageResponse? {
throw NotImplementedError() throw NotImplementedError()
} }
@ -632,7 +664,8 @@ fun TvType.isAnimeOp(): Boolean {
data class SubtitleFile(val lang: String, val url: String) data class SubtitleFile(val lang: String, val url: String)
data class HomePageResponse( data class HomePageResponse(
val items: List<HomePageList> val items: List<HomePageList>,
val hasNext: Boolean = false
) )
data class HomePageList( data class HomePageList(

View file

@ -104,7 +104,7 @@ class AllAnimeProvider : MainAPI() {
@JsonProperty("__typename") val _typename: String? = null @JsonProperty("__typename") val _typename: String? = null
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
// Pair( // Pair(

View file

@ -114,7 +114,7 @@ class AniPlayProvider : MainAPI() {
@JsonProperty("videoUrl") val url: String @JsonProperty("videoUrl") val url: String
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val response = app.get("$mainUrl/api/home/latest-episodes?page=0").parsed<List<ApiMainPageAnime>>() val response = app.get("$mainUrl/api/home/latest-episodes?page=0").parsed<List<ApiMainPageAnime>>()
val results = response.map{ val results = response.map{

View file

@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.animeproviders
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
import java.net.URLDecoder import java.net.URLDecoder
@ -45,7 +44,7 @@ class AniflixProvider : MainAPI() {
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val soup = app.get(mainUrl).document val soup = app.get(mainUrl).document
val elements = listOf( val elements = listOf(
@ -75,7 +74,6 @@ class AniflixProvider : MainAPI() {
val token = getToken() val token = getToken()
val url = "$mainUrl/_next/data/$token/search.json?keyword=$query" val url = "$mainUrl/_next/data/$token/search.json?keyword=$query"
val response = app.get(url) val response = app.get(url)
println("resp: $url ===> ${response.text}")
val searchResponse = val searchResponse =
response.parsedSafe<Search>() response.parsedSafe<Search>()
?: throw ErrorLoadingException("No Media") ?: throw ErrorLoadingException("No Media")

View file

@ -62,7 +62,7 @@ class AnimeIndoProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = request(mainUrl).document val document = request(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -58,7 +58,7 @@ class AnimePaheProvider : MainAPI() {
TvType.OVA TvType.OVA
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): 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,

View file

@ -47,7 +47,7 @@ class AnimeSailProvider : MainAPI() {
) )
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = request(mainUrl).document val document = request(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -64,7 +64,7 @@ class AnimeSaturnProvider : MainAPI() {
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val list = ArrayList<HomePageList>() val list = ArrayList<HomePageList>()
document.select("div.container:has(span.badge-saturn)").forEach { document.select("div.container:has(span.badge-saturn)").forEach {

View file

@ -130,7 +130,7 @@ class AnimeWorldProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = request(mainUrl).document val document = request(mainUrl).document
val list = ArrayList<HomePageList>() val list = ArrayList<HomePageList>()

View file

@ -30,7 +30,7 @@ class AnimefenixProvider:MainAPI() {
else DubStatus.Subbed else DubStatus.Subbed
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair("$mainUrl/", "Animes"), Pair("$mainUrl/", "Animes"),
Pair("$mainUrl/animes?type[]=movie&order=default", "Peliculas", ), Pair("$mainUrl/animes?type[]=movie&order=default", "Peliculas", ),

View file

@ -22,7 +22,7 @@ class AnimeflvIOProvider:MainAPI() {
TvType.OVA, TvType.OVA,
TvType.Anime, TvType.Anime,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/series", "Series actualizadas",), Pair("$mainUrl/series", "Series actualizadas",),

View file

@ -34,7 +34,7 @@ class AnimeflvnetProvider : MainAPI() {
TvType.Anime, TvType.Anime,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair("$mainUrl/browse?type[]=movie&order=updated", "Películas"), Pair("$mainUrl/browse?type[]=movie&order=updated", "Películas"),
Pair("$mainUrl/browse?status[]=2&order=default", "Animes"), Pair("$mainUrl/browse?status[]=2&order=default", "Animes"),

View file

@ -26,7 +26,7 @@ class AnimekisaProvider : MainAPI() {
@JsonProperty("html") val html: String @JsonProperty("html") val html: String
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair("$mainUrl/ajax/list/views?type=all", "All animes"), Pair("$mainUrl/ajax/list/views?type=all", "All animes"),
Pair("$mainUrl/ajax/list/views?type=day", "Trending now"), Pair("$mainUrl/ajax/list/views?type=day", "Trending now"),

View file

@ -96,7 +96,7 @@ class DubbedAnimeProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val trendingUrl = "$mainUrl/xz/trending.php?_=$unixTimeMS" val trendingUrl = "$mainUrl/xz/trending.php?_=$unixTimeMS"
val lastEpisodeUrl = "$mainUrl/xz/epgrid.php?p=1&_=$unixTimeMS" val lastEpisodeUrl = "$mainUrl/xz/epgrid.php?p=1&_=$unixTimeMS"
val recentlyAddedUrl = "$mainUrl/xz/gridgrabrecent.php?p=1&_=$unixTimeMS" val recentlyAddedUrl = "$mainUrl/xz/gridgrabrecent.php?p=1&_=$unixTimeMS"

View file

@ -178,7 +178,7 @@ class GogoanimeProvider : MainAPI() {
TvType.OVA TvType.OVA
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val headers = mapOf( val headers = mapOf(
"authority" to "ajax.gogo-load.com", "authority" to "ajax.gogo-load.com",
"sec-ch-ua" to "\"Google Chrome\";v=\"89\", \"Chromium\";v=\"89\", \";Not A Brand\";v=\"99\"", "sec-ch-ua" to "\"Google Chrome\";v=\"89\", \"Chromium\";v=\"89\", \";Not A Brand\";v=\"99\"",

View file

@ -46,7 +46,7 @@ class GomunimeProvider : MainAPI() {
@JsonProperty("html") val html: String @JsonProperty("html") val html: String
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair("e", "Episode Baru"), Pair("e", "Episode Baru"),
Pair("c", "Completed"), Pair("c", "Completed"),

View file

@ -32,7 +32,7 @@ class JKAnimeProvider : MainAPI() {
TvType.Anime, TvType.Anime,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair( Pair(
"$mainUrl/directorio/?filtro=fecha&tipo=TV&estado=1&fecha=none&temporada=none&orden=desc", "$mainUrl/directorio/?filtro=fecha&tipo=TV&estado=1&fecha=none&temporada=none&orden=desc",

View file

@ -14,7 +14,7 @@ class KawaiifuProvider : MainAPI() {
override val supportedTypes = setOf(TvType.Anime, TvType.AnimeMovie) override val supportedTypes = setOf(TvType.Anime, TvType.AnimeMovie)
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val resp = app.get(mainUrl).text val resp = app.get(mainUrl).text

View file

@ -17,7 +17,7 @@ class KimCartoonProvider : MainAPI() {
return if (url.startsWith("/")) mainUrl + url else url return if (url.startsWith("/")) mainUrl + url else url
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val doc = app.get(mainUrl).document.select("#container") val doc = app.get(mainUrl).document.select("#container")
val response = mutableListOf( val response = mutableListOf(
HomePageList( HomePageList(

View file

@ -38,7 +38,7 @@ class KuramanimeProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -39,7 +39,7 @@ class KuronimeProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -34,7 +34,7 @@ class MonoschinosProvider : MainAPI() {
TvType.Anime, TvType.Anime,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair("$mainUrl/emision", "En emisión"), Pair("$mainUrl/emision", "En emisión"),
Pair( Pair(

View file

@ -23,7 +23,7 @@ class MundoDonghuaProvider : MainAPI() {
TvType.Anime, TvType.Anime,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair("$mainUrl/lista-donghuas", "Donghuas"), Pair("$mainUrl/lista-donghuas", "Donghuas"),
) )

View file

@ -40,7 +40,7 @@ class NeonimeProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -137,44 +137,45 @@ class NineAnimeProvider : MainAPI() {
private fun decode(input: String): String = java.net.URLDecoder.decode(input, "utf-8") private fun decode(input: String): String = java.net.URLDecoder.decode(input, "utf-8")
} }
override suspend fun getMainPage(): HomePageResponse { override val mainPage = mainPageOf(
val items = listOf( "$mainUrl/ajax/home/widget/trending?page=" to "Trending",
"$mainUrl/ajax/home/widget/trending?page=1" to "Trending", "$mainUrl/ajax/home/widget/updated-all?page=" to "All",
"$mainUrl/ajax/home/widget/updated-all?page=1" to "All", "$mainUrl/ajax/home/widget/updated-sub?page=" to "Recently Updated (SUB)",
"$mainUrl/ajax/home/widget/updated-sub?page=1" to "Recently Updated (SUB)", "$mainUrl/ajax/home/widget/updated-dub?page=" to "Recently Updated (DUB)",
"$mainUrl/ajax/home/widget/updated-dub?page=1" to "$mainUrl/ajax/home/widget/updated-china?page=" to "Recently Updated (Chinese)",
"Recently Updated (DUB)", "$mainUrl/ajax/home/widget/random?page=" to "Random",
"$mainUrl/ajax/home/widget/updated-china?page=1" to )
"Recently Updated (Chinese)",
"$mainUrl/ajax/home/widget/random?page=1" to "Random",
).apmap { (url, name) ->
val home = Jsoup.parse(
app.get(
url
).parsed<Response>().html
).select("div.item").mapNotNull { element ->
val title = element.selectFirst(".info > .name") ?: return@mapNotNull null
val link = title.attr("href")
val poster = element.selectFirst(".poster > a > img")?.attr("src")
val meta = element.selectFirst(".poster > a > .meta > .inner > .left")
val subbedEpisodes = meta?.selectFirst(".sub")?.text()?.toIntOrNull()
val dubbedEpisodes = meta?.selectFirst(".dub")?.text()?.toIntOrNull()
newAnimeSearchResponse(title.text() ?: return@mapNotNull null, link) { override suspend fun getMainPage(
this.posterUrl = poster page: Int,
addDubStatus( categoryName: String,
dubbedEpisodes != null, categoryData: String
subbedEpisodes != null, ): HomePageResponse {
dubbedEpisodes, val url = categoryData + page
subbedEpisodes val home = Jsoup.parse(
) app.get(
} url
).parsed<Response>().html
).select("div.item").mapNotNull { element ->
val title = element.selectFirst(".info > .name") ?: return@mapNotNull null
val link = title.attr("href")
val poster = element.selectFirst(".poster > a > img")?.attr("src")
val meta = element.selectFirst(".poster > a > .meta > .inner > .left")
val subbedEpisodes = meta?.selectFirst(".sub")?.text()?.toIntOrNull()
val dubbedEpisodes = meta?.selectFirst(".dub")?.text()?.toIntOrNull()
newAnimeSearchResponse(title.text() ?: return@mapNotNull null, link) {
this.posterUrl = poster
addDubStatus(
dubbedEpisodes != null,
subbedEpisodes != null,
dubbedEpisodes,
subbedEpisodes
)
} }
HomePageList(name, home)
} }
return HomePageResponse(items) return newHomePageResponse(categoryName, home)
} }
data class Response( data class Response(

View file

@ -40,7 +40,7 @@ class NontonAnimeIDProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -40,7 +40,7 @@ class OploverzProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -38,7 +38,7 @@ class OtakudesuProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -44,7 +44,7 @@ class TenshiProvider : MainAPI() {
} }
}*/ }*/
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val soup = app.get(mainUrl, interceptor = ddosGuardKiller).document val soup = app.get(mainUrl, interceptor = ddosGuardKiller).document
for (section in soup.select("#content > section")) { for (section in soup.select("#content > section")) {

View file

@ -39,7 +39,7 @@ class TocanimeProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -30,7 +30,7 @@ class WcoProvider : MainAPI() {
TvType.OVA TvType.OVA
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): 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"),

View file

@ -77,7 +77,7 @@ class ZoroProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val html = app.get("$mainUrl/home").text val html = app.get("$mainUrl/home").text
val document = Jsoup.parse(html) val document = Jsoup.parse(html)

View file

@ -37,7 +37,7 @@ class EjaTv : MainAPI() {
) )
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
// Maybe this based on app language or as setting? // Maybe this based on app language or as setting?
val language = "English" val language = "English"
val dataMap = mapOf( val dataMap = mapOf(

View file

@ -196,7 +196,7 @@ open class TmdbProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
// SAME AS DISCOVER IT SEEMS // SAME AS DISCOVER IT SEEMS
// val popularSeries = tmdb.tvService().popular(1, "en-US").execute().body()?.results?.map { // val popularSeries = tmdb.tvService().popular(1, "en-US").execute().body()?.results?.map {

View file

@ -34,7 +34,7 @@ class AkwamProvider : MainAPI() {
) )
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
// Title, Url // Title, Url
val moviesUrl = listOf( val moviesUrl = listOf(
"Movies" to "$mainUrl/movies", "Movies" to "$mainUrl/movies",

View file

@ -27,7 +27,7 @@ class AllMoviesForYouProvider : MainAPI() {
TvType.TvSeries TvType.TvSeries
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val soup = app.get(mainUrl).document val soup = app.get(mainUrl).document
val urls = listOf( val urls = listOf(

View file

@ -18,7 +18,7 @@ class AltadefinizioneProvider : MainAPI() {
TvType.Movie TvType.Movie
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/azione/", "Azione"), Pair("$mainUrl/azione/", "Azione"),

View file

@ -110,7 +110,7 @@ class AsiaFlixProvider : MainAPI() {
) )
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val headers = mapOf("X-Requested-By" to "asiaflix-web") val headers = mapOf("X-Requested-By" to "asiaflix-web")
val response = app.get("$apiUrl/dashboard", headers = headers).text val response = app.get("$apiUrl/dashboard", headers = headers).text

View file

@ -23,7 +23,7 @@ open class BflixProvider : MainAPI() {
//override val uniqueId: Int by lazy { "BflixProvider".hashCode() } //override val uniqueId: Int by lazy { "BflixProvider".hashCode() }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val soup = app.get("$mainUrl/home").document val soup = app.get("$mainUrl/home").document
val testa = listOf( val testa = listOf(

View file

@ -41,7 +41,7 @@ class CimaNowProvider : MainAPI() {
) )
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val doc = app.get("$mainUrl/home", headers = mapOf("user-agent" to "MONKE")).document val doc = app.get("$mainUrl/home", headers = mapOf("user-agent" to "MONKE")).document
val pages = doc.select("section").not("section:contains(أختر وجهتك المفضلة)").not("section:contains(تم اضافته حديثاً)").apmap { val pages = doc.select("section").not("section:contains(أختر وجهتك المفضلة)").not("section:contains(تم اضافته حديثاً)").apmap {

View file

@ -17,7 +17,7 @@ class CineblogProvider : MainAPI() {
TvType.TvSeries, TvType.TvSeries,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/genere/azione/", "Azione"), Pair("$mainUrl/genere/azione/", "Azione"),

View file

@ -19,7 +19,7 @@ class CinecalidadProvider : MainAPI() {
) )
override val vpnStatus = VPNStatus.MightBeNeeded //Due to evoload sometimes not loading override val vpnStatus = VPNStatus.MightBeNeeded //Due to evoload sometimes not loading
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/ver-serie/", "Series"), Pair("$mainUrl/ver-serie/", "Series"),

View file

@ -19,7 +19,7 @@ class CuevanaProvider : MainAPI() {
TvType.TvSeries, TvType.TvSeries,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair(mainUrl, "Recientemente actualizadas"), Pair(mainUrl, "Recientemente actualizadas"),

View file

@ -31,7 +31,7 @@ class DoramasYTProvider : MainAPI() {
TvType.AsianDrama, TvType.AsianDrama,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair("$mainUrl/emision", "En emisión"), Pair("$mainUrl/emision", "En emisión"),
Pair( Pair(

View file

@ -14,7 +14,7 @@ class DramaSeeProvider : MainAPI() {
override val hasDownloadSupport = true override val hasDownloadSupport = true
override val supportedTypes = setOf(TvType.AsianDrama) override val supportedTypes = setOf(TvType.AsianDrama)
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val headers = mapOf("X-Requested-By" to mainUrl) val headers = mapOf("X-Requested-By" to mainUrl)
val document = app.get(mainUrl, headers = headers).document val document = app.get(mainUrl, headers = headers).document
val mainbody = document.getElementsByTag("body") val mainbody = document.getElementsByTag("body")

View file

@ -30,7 +30,7 @@ class DramaidProvider : MainAPI() {
} }
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -41,7 +41,7 @@ class EgyBestProvider : MainAPI() {
) )
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
// url, title // url, title
val doc = app.get(mainUrl).document val doc = app.get(mainUrl).document
val pages = arrayListOf<HomePageList>() val pages = arrayListOf<HomePageList>()

View file

@ -15,7 +15,7 @@ class ElifilmsProvider : MainAPI() {
TvType.Movie, TvType.Movie,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val newest = app.get(mainUrl).document.selectFirst("a.fav_link.premiera")?.attr("href") val newest = app.get(mainUrl).document.selectFirst("a.fav_link.premiera")?.attr("href")
val urls = listOf( val urls = listOf(

View file

@ -18,7 +18,7 @@ class EntrepeliculasyseriesProvider:MainAPI() {
) )
override val vpnStatus = VPNStatus.MightBeNeeded //Due to evoload sometimes not loading override val vpnStatus = VPNStatus.MightBeNeeded //Due to evoload sometimes not loading
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/series/", "Series"), Pair("$mainUrl/series/", "Series"),

View file

@ -28,7 +28,7 @@ class EstrenosDoramasProvider : MainAPI() {
TvType.AsianDrama, TvType.AsianDrama,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair(mainUrl, "Últimas series"), Pair(mainUrl, "Últimas series"),
Pair("$mainUrl/category/peliculas", "Películas"), Pair("$mainUrl/category/peliculas", "Películas"),

View file

@ -35,7 +35,7 @@ class FaselHDProvider : MainAPI() {
) )
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
// Title, Url // Title, Url
val moviesUrl = listOf( val moviesUrl = listOf(
Pair("Movies", "$mainUrl/all-movies/page/"+(0..10).random()), Pair("Movies", "$mainUrl/all-movies/page/"+(0..10).random()),

View file

@ -18,7 +18,7 @@ class FilmanProvider : MainAPI() {
TvType.TvSeries TvType.TvSeries
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val lists = document.select("#item-list,#series-list") val lists = document.select("#item-list,#series-list")
val categories = ArrayList<HomePageList>() val categories = ArrayList<HomePageList>()

View file

@ -23,7 +23,7 @@ class FilmpertuttiProvider : MainAPI() {
TvType.TvSeries TvType.TvSeries
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/category/serie-tv/", "Serie Tv"), Pair("$mainUrl/category/serie-tv/", "Serie Tv"),

View file

@ -232,7 +232,7 @@ class FrenchStreamProvider : MainAPI() {
} }
override suspend fun getMainPage(): HomePageResponse? { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse? {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val docs = document.select("div.sect") val docs = document.select("div.sect")
val returnList = docs.mapNotNull { val returnList = docs.mapNotNull {

View file

@ -69,7 +69,7 @@ class HDMProvider : MainAPI() {
) )
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val html = app.get(mainUrl, timeout = 25).text val html = app.get(mainUrl, timeout = 25).text
val document = Jsoup.parse(html) val document = Jsoup.parse(html)
val all = ArrayList<HomePageList>() val all = ArrayList<HomePageList>()

View file

@ -19,7 +19,7 @@ class HDMovie5 : MainAPI() {
TvType.TvSeries, TvType.TvSeries,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val doc = app.get(mainUrl).document.select("div.content") val doc = app.get(mainUrl).document.select("div.content")
val list = mapOf( val list = mapOf(
"Featured Movies" to "featured", "Featured Movies" to "featured",

View file

@ -28,7 +28,7 @@ class HDrezkaProvider : MainAPI() {
TvType.AsianDrama TvType.AsianDrama
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()

View file

@ -14,7 +14,7 @@ class IHaveNoTvProvider : MainAPI() {
override val supportedTypes = setOf(TvType.Documentary) override val supportedTypes = setOf(TvType.Documentary)
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
// Uhh, I am too lazy to scrape the "latest documentaries" and "recommended documentaries", // Uhh, I am too lazy to scrape the "latest documentaries" and "recommended documentaries",
// so I am just scraping 3 random categories // so I am just scraping 3 random categories
val allCategories = listOf( val allCategories = listOf(

View file

@ -22,7 +22,7 @@ class IdlixProvider : MainAPI() {
TvType.TvSeries, TvType.TvSeries,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -26,7 +26,7 @@ class KdramaHoodProvider : MainAPI() {
@JsonProperty("file") val file: String @JsonProperty("file") val file: String
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val doc = app.get("$mainUrl/home2").document val doc = app.get("$mainUrl/home2").document
val home = ArrayList<HomePageList>() val home = ArrayList<HomePageList>()

View file

@ -21,7 +21,7 @@ class LayarKacaProvider : MainAPI() {
TvType.AsianDrama TvType.AsianDrama
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -22,7 +22,7 @@ class MultiplexProvider : MainAPI() {
TvType.AsianDrama TvType.AsianDrama
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -44,7 +44,7 @@ class MyCimaProvider : MainAPI() {
) )
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
// Title, Url // Title, Url
val moviesUrl = listOf( val moviesUrl = listOf(
"Movies" to "$mainUrl/movies/page/" + (0..25).random(), "Movies" to "$mainUrl/movies/page/" + (0..25).random(),

View file

@ -212,7 +212,7 @@ class NginxProvider : MainAPI() {
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val authHeader = val authHeader =
getAuthHeader() // call again because it isn't reloaded if in main class and storedCredentials loads after getAuthHeader() // call again because it isn't reloaded if in main class and storedCredentials loads after

View file

@ -17,7 +17,7 @@ class PeliSmartProvider: MainAPI() {
) )
override val vpnStatus = VPNStatus.MightBeNeeded //Due to evoload sometimes not loading override val vpnStatus = VPNStatus.MightBeNeeded //Due to evoload sometimes not loading
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/peliculas/", "Peliculas"), Pair("$mainUrl/peliculas/", "Peliculas"),

View file

@ -18,7 +18,7 @@ class PelisflixProvider : MainAPI() {
TvType.TvSeries, TvType.TvSeries,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/ver-peliculas-online-gratis-fullhdc3/", "Películas"), Pair("$mainUrl/ver-peliculas-online-gratis-fullhdc3/", "Películas"),

View file

@ -16,7 +16,7 @@ class PelisplusHDProvider:MainAPI() {
TvType.Movie, TvType.Movie,
TvType.TvSeries, TvType.TvSeries,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val map = mapOf( val map = mapOf(

View file

@ -142,7 +142,7 @@ open class PelisplusProviderTemplate : MainAPI() {
// This loads the homepage, which is basically a collection of search results with labels. // This loads the homepage, which is basically a collection of search results with labels.
// Optional function, but make sure to enable hasMainPage if you program this. // Optional function, but make sure to enable hasMainPage if you program this.
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = homePageUrlList val urls = homePageUrlList
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()
// .pmap {} is used to fetch the different pages in parallel // .pmap {} is used to fetch the different pages in parallel

View file

@ -23,7 +23,7 @@ class PhimmoichillProvider : MainAPI() {
TvType.AsianDrama TvType.AsianDrama
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -15,7 +15,7 @@ class PinoyHDXyzProvider : MainAPI() {
override val hasMainPage = true override val hasMainPage = true
override val hasQuickSearch = false override val hasQuickSearch = false
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val all = ArrayList<HomePageList>() val all = ArrayList<HomePageList>()
val document = app.get(mainUrl, referer = mainUrl).document val document = app.get(mainUrl, referer = mainUrl).document
val mainbody = document.getElementsByTag("body") val mainbody = document.getElementsByTag("body")

View file

@ -16,7 +16,7 @@ class PinoyMoviePediaProvider : MainAPI() {
override val hasMainPage = true override val hasMainPage = true
override val hasQuickSearch = false override val hasQuickSearch = false
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val all = ArrayList<HomePageList>() val all = ArrayList<HomePageList>()
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val mainbody = document.getElementsByTag("body") val mainbody = document.getElementsByTag("body")

View file

@ -90,7 +90,7 @@ class PinoyMoviesEsProvider : MainAPI() {
return all return all
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val all = ArrayList<HomePageList>() val all = ArrayList<HomePageList>()
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val mainbody = document.getElementsByTag("body") val mainbody = document.getElementsByTag("body")

View file

@ -27,7 +27,7 @@ class RebahinProvider : MainAPI() {
TvType.AsianDrama TvType.AsianDrama
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = listOf( val urls = listOf(
Pair("Featured", "xtab1"), Pair("Featured", "xtab1"),
Pair("Film Terbaru", "xtab2"), Pair("Film Terbaru", "xtab2"),

View file

@ -18,7 +18,7 @@ class SeriesflixProvider : MainAPI() {
TvType.TvSeries, TvType.TvSeries,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/ver-series-online/", "Series"), Pair("$mainUrl/ver-series-online/", "Series"),

View file

@ -43,7 +43,7 @@ open class SflixProvider : MainAPI() {
) )
override val vpnStatus = VPNStatus.None override val vpnStatus = VPNStatus.None
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val html = app.get("$mainUrl/home").text val html = app.get("$mainUrl/home").text
val document = Jsoup.parse(html) val document = Jsoup.parse(html)

View file

@ -19,7 +19,7 @@ class SoaptwoDayProvider : MainAPI() {
TvType.TvSeries, TvType.TvSeries,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/movielist/", "Movies"), Pair("$mainUrl/movielist/", "Movies"),

View file

@ -174,7 +174,7 @@ class StreamingcommunityProvider : MainAPI() {
val posterMap = hashMapOf<String, String>() val posterMap = hashMapOf<String, String>()
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
document.select("slider-title").subList(0, 6).map { it -> document.select("slider-title").subList(0, 6).map { it ->

View file

@ -18,7 +18,7 @@ class TantifilmProvider : MainAPI() {
TvType.TvSeries, TvType.TvSeries,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val urls = listOf( val urls = listOf(
Pair("$mainUrl/watch-genre/serie-tv/", "Serie Tv"), Pair("$mainUrl/watch-genre/serie-tv/", "Serie Tv"),

View file

@ -143,7 +143,7 @@ class TheFlixToProvider : MainAPI() {
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val items = ArrayList<HomePageList>() val items = ArrayList<HomePageList>()
val doc = app.get(mainUrl).document val doc = app.get(mainUrl).document
val scriptText = doc.selectFirst("script[type=application/json]")!!.data() val scriptText = doc.selectFirst("script[type=application/json]")!!.data()

View file

@ -23,7 +23,7 @@ class UakinoProvider : MainAPI() {
TvType.Anime TvType.Anime
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -210,7 +210,7 @@ open class VidstreamProviderTemplate : MainAPI() {
// This loads the homepage, which is basically a collection of search results with labels. // This loads the homepage, which is basically a collection of search results with labels.
// Optional function, but make sure to enable hasMainPage if you program this. // Optional function, but make sure to enable hasMainPage if you program this.
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val urls = homePageUrlList val urls = homePageUrlList
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()
// .pmap {} is used to fetch the different pages in parallel // .pmap {} is used to fetch the different pages in parallel

View file

@ -18,7 +18,7 @@ class WatchAsianProvider : MainAPI() {
override val hasDownloadSupport = true override val hasDownloadSupport = true
override val supportedTypes = setOf(TvType.AsianDrama) override val supportedTypes = setOf(TvType.AsianDrama)
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val headers = mapOf("X-Requested-By" to mainUrl) val headers = mapOf("X-Requested-By" to mainUrl)
val doc = app.get(mainUrl, headers = headers).document val doc = app.get(mainUrl, headers = headers).document
val rowPair = mutableListOf<Pair<String, String>>() val rowPair = mutableListOf<Pair<String, String>>()

View file

@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.movieproviders
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@ -41,7 +40,7 @@ class XcineProvider : MainAPI() {
) )
} }
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val sections = document.select("div.group-film") val sections = document.select("div.group-film")
return HomePageResponse(sections.mapNotNull { section -> return HomePageResponse(sections.mapNotNull { section ->

View file

@ -19,7 +19,7 @@ class YomoviesProvider : MainAPI() {
TvType.TvSeries, TvType.TvSeries,
) )
override suspend fun getMainPage(): HomePageResponse { override suspend fun getMainPage(page: Int, categoryName: String, categoryData: String): HomePageResponse {
val document = app.get(mainUrl).document val document = app.get(mainUrl).document
val homePageList = ArrayList<HomePageList>() val homePageList = ArrayList<HomePageList>()

View file

@ -4,6 +4,7 @@ import android.util.Log
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.bumptech.glide.load.HttpException import com.bumptech.glide.load.HttpException
import com.lagradost.cloudstream3.BuildConfig
import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.ErrorLoadingException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -11,6 +12,34 @@ import java.net.SocketTimeoutException
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.net.ssl.SSLHandshakeException import javax.net.ssl.SSLHandshakeException
const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!"
class DebugException(message: String) : Exception("$DEBUG_EXCEPTION\n$message")
inline fun debugException(message: () -> String) {
if (BuildConfig.DEBUG) {
throw DebugException(message.invoke())
}
}
inline fun debugWarning(message: () -> String) {
if (BuildConfig.DEBUG) {
logError(DebugException(message.invoke()))
}
}
inline fun debugAssert(assert: () -> Boolean, message: () -> String) {
if (BuildConfig.DEBUG && assert.invoke()) {
throw DebugException(message.invoke())
}
}
inline fun debugWarning(assert: () -> Boolean, message: () -> String) {
if (BuildConfig.DEBUG && assert.invoke()) {
logError(DebugException(message.invoke()))
}
}
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) { fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
liveData.observe(this) { it?.let { t -> action(t) } } liveData.observe(this) { it?.let { t -> action(t) } }
} }
@ -61,11 +90,12 @@ suspend fun <T> suspendSafeApiCall(apiCall: suspend () -> T): T? {
} }
fun <T> safeFail(throwable: Throwable): Resource<T> { fun <T> safeFail(throwable: Throwable): Resource<T> {
val stackTraceMsg = (throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString( val stackTraceMsg =
separator = "\n" (throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString(
) { separator = "\n"
"${it.fileName} ${it.lineNumber}" ) {
} "${it.fileName} ${it.lineNumber}"
}
return Resource.Failure(false, null, null, stackTraceMsg) return Resource.Failure(false, null, null, stackTraceMsg)
} }
@ -92,16 +122,31 @@ suspend fun <T> safeApiCall(
safeFail(throwable) safeFail(throwable)
} }
is SocketTimeoutException -> { is SocketTimeoutException -> {
Resource.Failure(true, null, null, "Connection Timeout\nPlease try again later.") Resource.Failure(
true,
null,
null,
"Connection Timeout\nPlease try again later."
)
} }
is HttpException -> { is HttpException -> {
Resource.Failure(false, throwable.statusCode, null, throwable.message ?: "HttpException") Resource.Failure(
false,
throwable.statusCode,
null,
throwable.message ?: "HttpException"
)
} }
is UnknownHostException -> { is UnknownHostException -> {
Resource.Failure(true, null, null, "Cannot connect to server, try again later.") Resource.Failure(true, null, null, "Cannot connect to server, try again later.")
} }
is ErrorLoadingException -> { is ErrorLoadingException -> {
Resource.Failure(true, null, null, throwable.message ?: "Error loading, try again later.") Resource.Failure(
true,
null,
null,
throwable.message ?: "Error loading, try again later."
)
} }
is NotImplementedError -> { is NotImplementedError -> {
Resource.Failure(false, null, null, "This operation is not implemented.") Resource.Failure(false, null, null, "This operation is not implemented.")

View file

@ -29,6 +29,7 @@ class APIRepository(val api: MainAPI) {
val hasMainPage = api.hasMainPage val hasMainPage = api.hasMainPage
val name = api.name val name = api.name
val mainUrl = api.mainUrl val mainUrl = api.mainUrl
val mainPage = api.mainPage
val hasQuickSearch = api.hasQuickSearch val hasQuickSearch = api.hasQuickSearch
suspend fun load(url: String): Resource<LoadResponse> { suspend fun load(url: String): Resource<LoadResponse> {
@ -59,9 +60,13 @@ class APIRepository(val api: MainAPI) {
} }
} }
suspend fun getMainPage(): Resource<HomePageResponse?> { suspend fun getMainPage(page: Int, nameIndex: Int? = null): Resource<List<HomePageResponse?>> {
return safeApiCall { return safeApiCall {
api.getMainPage() ?: throw ErrorLoadingException() nameIndex?.let { api.mainPage.getOrNull(it) }?.let { data ->
listOf(api.getMainPage(page, data.name, data.data))
} ?: api.mainPage.apmap { data ->
api.getMainPage(page, data.name, data.data)
}
} }
} }

View file

@ -24,6 +24,7 @@ class HomeChildItemAdapter(
) : ) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var isHorizontal: Boolean = false var isHorizontal: Boolean = false
var hasNext : Boolean = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = overrideLayout val layout = overrideLayout

View file

@ -20,7 +20,6 @@ import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.LinearSnapHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -44,11 +43,12 @@ import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
import com.lagradost.cloudstream3.ui.search.* import com.lagradost.cloudstream3.ui.search.*
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppUtils.setMaxViewPoolSize
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper
@ -122,11 +122,27 @@ class HomeFragment : Fragment() {
val errorProfilePic = errorProfilePics.random() val errorProfilePic = errorProfilePics.random()
fun Activity.loadHomepageList(item: HomePageList, deleteCallback: (() -> Unit)? = null) { fun Activity.loadHomepageList(
item: HomePageList,
deleteCallback: (() -> Unit)? = null,
) {
loadHomepageList(
expand = HomeViewModel.ExpandableHomepageList(item, 1, false),
deleteCallback = deleteCallback,
expandCallback = null
)
}
fun Activity.loadHomepageList(
expand: HomeViewModel.ExpandableHomepageList,
deleteCallback: (() -> Unit)? = null,
expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null
) {
val context = this val context = this
val bottomSheetDialogBuilder = BottomSheetDialog(context) val bottomSheetDialogBuilder = BottomSheetDialog(context)
bottomSheetDialogBuilder.setContentView(R.layout.home_episodes_expanded) bottomSheetDialogBuilder.setContentView(R.layout.home_episodes_expanded)
val title = bottomSheetDialogBuilder.findViewById<TextView>(R.id.home_expanded_text)!! val title = bottomSheetDialogBuilder.findViewById<TextView>(R.id.home_expanded_text)!!
val item = expand.list
title.text = item.name title.text = item.name
val recycle = val recycle =
bottomSheetDialogBuilder.findViewById<AutofitRecyclerView>(R.id.home_expanded_recycler)!! bottomSheetDialogBuilder.findViewById<AutofitRecyclerView>(R.id.home_expanded_recycler)!!
@ -179,8 +195,36 @@ class HomeFragment : Fragment() {
if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) { if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) {
bottomSheetDialogBuilder.dismissSafe(this) bottomSheetDialogBuilder.dismissSafe(this)
} }
}.apply {
hasNext = expand.hasNext
} }
recycle.addOnScrollListener(object : RecyclerView.OnScrollListener() {
var expandCount = 0
val name = expand.list.name
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val adapter = recyclerView.adapter
if (adapter !is SearchAdapter) return
val count = adapter.itemCount
val currentHasNext = adapter.hasNext
if (!recyclerView.canScrollVertically(1) && currentHasNext && expandCount != count) {
expandCount = count
ioSafe {
expandCallback?.invoke(name)?.let { newExpand ->
(recyclerView.adapter as? SearchAdapter?)?.apply {
hasNext = newExpand.hasNext
updateList(newExpand.list.list)
}
}
}
}
}
})
val spanListener = { span: Int -> val spanListener = { span: Int ->
recycle.spanCount = span recycle.spanCount = span
//(recycle.adapter as SearchAdapter).notifyDataSetChanged() //(recycle.adapter as SearchAdapter).notifyDataSetChanged()
@ -349,8 +393,6 @@ class HomeFragment : Fragment() {
return inflater.inflate(layout, container, false) return inflater.inflate(layout, container, false)
} }
private var currentHomePage: HomePageResponse? = null
private fun toggleMainVisibility(visible: Boolean) { private fun toggleMainVisibility(visible: Boolean) {
home_main_holder?.isVisible = visible home_main_holder?.isVisible = visible
home_main_poster_recyclerview?.isVisible = visible home_main_poster_recyclerview?.isVisible = visible
@ -541,18 +583,11 @@ class HomeFragment : Fragment() {
val d = data.value val d = data.value
listHomepageItems.clear() listHomepageItems.clear()
currentHomePage = d // println("ITEMCOUNT: ${d.values.size} ${home_master_recycler?.adapter?.itemCount}")
(home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList( (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(
d?.items?.mapNotNull { d.values.toMutableList(),
try { home_master_recycler
val filter = it.list.filterSearchResponse() )
listHomepageItems.addAll(filter)
it.copy(list = filter)
} catch (e: Exception) {
logError(e)
null
}
} ?: listOf())
home_loading?.isVisible = false home_loading?.isVisible = false
home_loading_error?.isVisible = false home_loading_error?.isVisible = false
@ -603,13 +638,6 @@ class HomeFragment : Fragment() {
} }
} }
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
ParentItemAdapter(mutableListOf(), { callback ->
homeHandleSearch(callback)
}, { item ->
activity?.loadHomepageList(item)
})
val toggleList = listOf( val toggleList = listOf(
Pair(home_type_watching_btt, WatchType.WATCHING), Pair(home_type_watching_btt, WatchType.WATCHING),
Pair(home_type_completed_btt, WatchType.COMPLETED), Pair(home_type_completed_btt, WatchType.COMPLETED),
@ -861,9 +889,22 @@ class HomeFragment : Fragment() {
context?.fixPaddingStatusbarView(home_statusbar) context?.fixPaddingStatusbarView(home_statusbar)
context?.fixPaddingStatusbar(home_loading_statusbar) context?.fixPaddingStatusbar(home_loading_statusbar)
home_master_recycler.adapter =
home_master_recycler.adapter = adapter ParentItemAdapter(mutableListOf(), { callback ->
home_master_recycler.layoutManager = GridLayoutManager(context, 1) homeHandleSearch(callback)
}, { item ->
activity?.loadHomepageList(item, expandCallback = {
homeViewModel.expandAndReturn(it)
})
}, { name ->
homeViewModel.expand(name)
})
home_master_recycler?.setMaxViewPoolSize(0, Int.MAX_VALUE)
home_master_recycler.layoutManager = object : LinearLayoutManager(context) {
override fun supportsPredictiveItemAnimations(): Boolean {
return false
}
} // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() }
if (context?.isTvSettings() == false) { if (context?.isTvSettings() == false) {
LinearSnapHelper().attachToRecyclerView(home_main_poster_recyclerview) // snap LinearSnapHelper().attachToRecyclerView(home_main_poster_recyclerview) // snap

View file

@ -6,29 +6,38 @@ import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import kotlinx.android.synthetic.main.homepage_parent.view.* import kotlinx.android.synthetic.main.homepage_parent.view.*
class ParentItemAdapter( class ParentItemAdapter(
private var items: MutableList<HomePageList>, private var items: MutableList<HomeViewModel.ExpandableHomepageList>,
private val clickCallback: (SearchClickCallback) -> Unit, private val clickCallback: (SearchClickCallback) -> Unit,
private val moreInfoClickCallback: (HomePageList) -> Unit, private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
private val expandCallback: ((String) -> Unit)? = null,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, i: Int): ParentViewHolder { override fun onCreateViewHolder(parent: ViewGroup, i: Int): ParentViewHolder {
//println("onCreateViewHolder $i")
val layout = val layout =
if (parent.context.isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent if (parent.context.isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent
return ParentViewHolder( return ParentViewHolder(
LayoutInflater.from(parent.context).inflate(layout, parent, false), LayoutInflater.from(parent.context).inflate(layout, parent, false),
clickCallback, clickCallback,
moreInfoClickCallback moreInfoClickCallback,
expandCallback
) )
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
//println("onBindViewHolder $position")
when (holder) { when (holder) {
is ParentViewHolder -> { is ParentViewHolder -> {
holder.bind(items[position]) holder.bind(items[position])
@ -41,44 +50,113 @@ class ParentItemAdapter(
} }
override fun getItemId(position: Int): Long { override fun getItemId(position: Int): Long {
return items[position].name.hashCode().toLong() return items[position].list.name.hashCode().toLong()
} }
@JvmName("updateListHomePageList")
fun updateList(newList: List<HomePageList>) { fun updateList(newList: List<HomePageList>) {
// this moves all bad results to the bottom updateList(newList.map { HomeViewModel.ExpandableHomepageList(it, 1, false) }
val endList = mutableListOf<HomePageList>() .toMutableList())
val newFilteredList = mutableListOf<HomePageList>() }
for (item in newList) {
if (item.list.isEmpty()) { @JvmName("updateListExpandableHomepageList")
endList.add(item) fun updateList(
} else { newList: MutableList<HomeViewModel.ExpandableHomepageList>,
newFilteredList.add(item) recyclerView: RecyclerView? = null
} ) {
} // this
newFilteredList.addAll(endList) // 1. prevents deep copy that makes this.items == newList
// 2. filters out undesirable results
// 3. moves empty results to the bottom (sortedBy is a stable sort)
val new =
newList.map { it.copy(list = it.list.copy(list = it.list.list.filterSearchResponse())) }
.sortedBy { it.list.list.isEmpty() }
val diffResult = DiffUtil.calculateDiff( val diffResult = DiffUtil.calculateDiff(
SearchDiffCallback(this.items, newFilteredList) SearchDiffCallback(items, new)
) )
items.clear() items.clear()
items.addAll(newFilteredList) items.addAll(new)
diffResult.dispatchUpdatesTo(this) val mAdapter = this
diffResult.dispatchUpdatesTo(object : ListUpdateCallback {
override fun onInserted(position: Int, count: Int) {
mAdapter.notifyItemRangeInserted(position, count)
}
override fun onRemoved(position: Int, count: Int) {
mAdapter.notifyItemRangeRemoved(position, count)
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
mAdapter.notifyItemMoved(fromPosition, toPosition)
}
override fun onChanged(position: Int, count: Int, payload: Any?) {
// I know kinda messy, what this does is using the update or bind instead of onCreateViewHolder -> bind
recyclerView?.apply {
// this loops every viewHolder in the recycle view and checks the position to see if it is within the update range
val missingUpdates = (position until (position + count)).toMutableSet()
for (i in 0 until mAdapter.itemCount) {
val viewHolder = getChildViewHolder(getChildAt(i))
val absolutePosition = viewHolder.absoluteAdapterPosition
if (absolutePosition >= position && absolutePosition < position + count) {
val expand = items.getOrNull(absolutePosition) ?: continue
if (viewHolder is ParentViewHolder) {
missingUpdates -= absolutePosition
if (viewHolder.title.text == expand.list.name) {
viewHolder.update(expand)
} else {
viewHolder.bind(expand)
}
}
}
}
// just in case some item did not get updated
for (i in missingUpdates) {
mAdapter.notifyItemChanged(i, payload)
}
} ?: run { // in case we don't have a nice
mAdapter.notifyItemRangeChanged(position, count, payload)
}
}
})
//diffResult.dispatchUpdatesTo(this)
} }
class ParentViewHolder class ParentViewHolder
constructor( constructor(
itemView: View, itemView: View,
private val clickCallback: (SearchClickCallback) -> Unit, private val clickCallback: (SearchClickCallback) -> Unit,
private val moreInfoClickCallback: (HomePageList) -> Unit private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
private val expandCallback: ((String) -> Unit)? = null,
) : ) :
RecyclerView.ViewHolder(itemView) { RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.home_parent_item_title val title: TextView = itemView.home_parent_item_title
val recyclerView: RecyclerView = itemView.home_child_recyclerview val recyclerView: RecyclerView = itemView.home_child_recyclerview
private val moreInfo: FrameLayout? = itemView.home_child_more_info private val moreInfo: FrameLayout? = itemView.home_child_more_info
fun bind(info: HomePageList) {
title.text = info.name fun update(expand: HomeViewModel.ExpandableHomepageList) {
val info = expand.list
(recyclerView.adapter as? HomeChildItemAdapter?)?.apply {
updateList(info.list.toMutableList())
hasNext = expand.hasNext
} ?: run {
recyclerView.adapter = HomeChildItemAdapter(
info.list.toMutableList(),
clickCallback = clickCallback,
nextFocusUp = recyclerView.nextFocusUpId,
nextFocusDown = recyclerView.nextFocusDownId,
).apply {
isHorizontal = info.isHorizontalImages
}
}
}
fun bind(expand: HomeViewModel.ExpandableHomepageList) {
val info = expand.list
recyclerView.adapter = HomeChildItemAdapter( recyclerView.adapter = HomeChildItemAdapter(
info.list.toMutableList(), info.list.toMutableList(),
clickCallback = clickCallback, clickCallback = clickCallback,
@ -86,29 +164,56 @@ class ParentItemAdapter(
nextFocusDown = recyclerView.nextFocusDownId, nextFocusDown = recyclerView.nextFocusDownId,
).apply { ).apply {
isHorizontal = info.isHorizontalImages isHorizontal = info.isHorizontalImages
hasNext = expand.hasNext
} }
title.text = info.name
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
var expandCount = 0
val name = expand.list.name
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val adapter = recyclerView.adapter
if (adapter !is HomeChildItemAdapter) return
val count = adapter.itemCount
val hasNext = adapter.hasNext
if (!recyclerView.canScrollHorizontally(1) && hasNext && expandCount != count) {
expandCount = count
expandCallback?.invoke(name)
}
}
})
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged() //(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
moreInfo?.setOnClickListener { moreInfo?.setOnClickListener {
moreInfoClickCallback.invoke(info) moreInfoClickCallback.invoke(expand)
} }
} }
} }
} }
class SearchDiffCallback( class SearchDiffCallback(
private val oldList: List<HomePageList>, private val oldList: List<HomeViewModel.ExpandableHomepageList>,
private val newList: List<HomePageList> private val newList: List<HomeViewModel.ExpandableHomepageList>
) : ) :
DiffUtil.Callback() { DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].name == newList[newItemPosition].name oldList[oldItemPosition].list.name == newList[newItemPosition].list.name
override fun getOldListSize() = oldList.size override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size override fun getNewListSize() = newList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition] == newList[newItemPosition] oldList[oldItemPosition] == newList[newItemPosition]
//{
// val ret = oldList[oldItemPosition].list.list.size == newList[newItemPosition].list.list.size
// println(">>>>>>>>>>>>>>>> $ret ${oldList[oldItemPosition].list.list.size} == ${newList[newItemPosition].list.list.size}")
// return ret
//}
} }

View file

@ -10,10 +10,12 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.AcraApplication.Companion.context import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.HomePageResponse import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.debugAssert
import com.lagradost.cloudstream3.mvvm.debugWarning
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
@ -34,6 +36,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import kotlin.collections.set
class HomeViewModel : ViewModel() { class HomeViewModel : ViewModel() {
private var repo: APIRepository? = null private var repo: APIRepository? = null
@ -41,9 +44,6 @@ class HomeViewModel : ViewModel() {
private val _apiName = MutableLiveData<String>() private val _apiName = MutableLiveData<String>()
val apiName: LiveData<String> = _apiName val apiName: LiveData<String> = _apiName
private val _page = MutableLiveData<Resource<HomePageResponse?>>()
val page: LiveData<Resource<HomePageResponse?>> = _page
private val _randomItems = MutableLiveData<List<SearchResponse>?>(null) private val _randomItems = MutableLiveData<List<SearchResponse>?>(null)
val randomItems: LiveData<List<SearchResponse>?> = _randomItems val randomItems: LiveData<List<SearchResponse>?> = _randomItems
@ -51,8 +51,10 @@ class HomeViewModel : ViewModel() {
return APIRepository(apis.first { it.hasMainPage }) return APIRepository(apis.first { it.hasMainPage })
} }
private val _availableWatchStatusTypes = MutableLiveData<Pair<EnumSet<WatchType>, EnumSet<WatchType>>>() private val _availableWatchStatusTypes =
val availableWatchStatusTypes: LiveData<Pair<EnumSet<WatchType>, EnumSet<WatchType>>> = _availableWatchStatusTypes MutableLiveData<Pair<EnumSet<WatchType>, EnumSet<WatchType>>>()
val availableWatchStatusTypes: LiveData<Pair<EnumSet<WatchType>, EnumSet<WatchType>>> =
_availableWatchStatusTypes
private val _bookmarks = MutableLiveData<Pair<Boolean, List<SearchResponse>>>() private val _bookmarks = MutableLiveData<Pair<Boolean, List<SearchResponse>>>()
val bookmarks: LiveData<Pair<Boolean, List<SearchResponse>>> = _bookmarks val bookmarks: LiveData<Pair<Boolean, List<SearchResponse>>> = _bookmarks
@ -143,6 +145,67 @@ class HomeViewModel : ViewModel() {
onGoingLoad = load(api) onGoingLoad = load(api)
} }
data class ExpandableHomepageList(
var list: HomePageList,
var currentPage: Int,
var hasNext: Boolean,
)
private val expandable: MutableMap<String, ExpandableHomepageList> = mutableMapOf()
private val _page =
MutableLiveData<Resource<Map<String, ExpandableHomepageList>>>(Resource.Loading())
val page: LiveData<Resource<Map<String, ExpandableHomepageList>>> = _page
val lock: MutableSet<String> = mutableSetOf()
suspend fun expandAndReturn(name: String) : ExpandableHomepageList? {
if (lock.contains(name)) return null
lock += name
repo?.apply {
expandable[name]?.let { current ->
debugAssert({ !current.hasNext }) {
"Expand called when not needed"
}
val nextPage = current.currentPage + 1
val next = getMainPage(nextPage, mainPage.indexOfFirst { it.name == name })
if (next is Resource.Success) {
next.value.filterNotNull().forEach { main ->
main.items.forEach { newList ->
val key = newList.name
expandable[key]?.apply {
hasNext = main.hasNext
currentPage = nextPage
debugWarning({ newList.list.any { outer -> this.list.list.any { it.url == outer.url } } }) {
"Expanded contained an item that was previously already in the list\n${list.name} = ${this.list.list}\n${newList.name} = ${newList.list}"
}
this.list.list += newList.list
this.list.list.distinctBy { it.url } // just to be sure we are not adding the same shit for some reason
} ?: debugWarning {
"Expanded an item not in main load named $key, current list is ${expandable.keys}"
}
}
}
} else {
current.hasNext = false
}
}
_page.postValue(Resource.Success(expandable))
}
lock -= name
return expandable[name]
}
// this is soo over engineered, but idk how I can make it clean without making the main api harder to use :pensive:
fun expand(name: String) = viewModelScope.launch {
expandAndReturn(name)
}
private fun load(api: MainAPI?) = viewModelScope.launch { private fun load(api: MainAPI?) = viewModelScope.launch {
repo = if (api != null) { repo = if (api != null) {
APIRepository(api) APIRepository(api)
@ -156,35 +219,43 @@ class HomeViewModel : ViewModel() {
if (repo?.hasMainPage == true) { if (repo?.hasMainPage == true) {
_page.postValue(Resource.Loading()) _page.postValue(Resource.Loading())
val data = repo?.getMainPage() when (val data = repo?.getMainPage(1, null)) {
when (data) {
is Resource.Success -> { is Resource.Success -> {
try { try {
val home = data.value expandable.clear()
if (home?.items?.isNullOrEmpty() == false) { data.value.forEach { home ->
home?.items?.forEach { list ->
expandable[list.name] =
ExpandableHomepageList(list, 1, home.hasNext)
}
}
_page.postValue(Resource.Success(expandable))
val items = data.value.mapNotNull { it?.items }.flatten()
//val home = data.value
if (items.isNotEmpty()) {
val currentList = val currentList =
home.items.shuffled().filter { !it.list.isNullOrEmpty() }.flatMap { it.list } items.shuffled().filter { it.list.isNotEmpty() }
.flatMap { it.list }
.distinctBy { it.url } .distinctBy { it.url }
.toList() .toList()
if (!currentList.isNullOrEmpty()) { if (currentList.isNotEmpty()) {
val randomItems = currentList.shuffled() val randomItems = currentList.shuffled()
_randomItems.postValue(randomItems) _randomItems.postValue(randomItems)
} }
} }
} catch (e : Exception) { } catch (e: Exception) {
_randomItems.postValue(emptyList()) _randomItems.postValue(emptyList())
logError(e) logError(e)
} }
} }
is Resource.Failure -> {
_page.postValue(data!!)
}
else -> Unit else -> Unit
} }
data?.let {
_page.postValue(it)
}
} else {
_page.postValue(Resource.Success(HomePageResponse(emptyList())))
} }
} }
@ -194,7 +265,7 @@ class HomeViewModel : ViewModel() {
loadAndCancel(noneApi) loadAndCancel(noneApi)
else if (preferredApiName == randomApi.name || api == null) { else if (preferredApiName == randomApi.name || api == null) {
val validAPIs = context?.filterProviderByPreferredMedia() val validAPIs = context?.filterProviderByPreferredMedia()
if(validAPIs.isNullOrEmpty()) { if (validAPIs.isNullOrEmpty()) {
loadAndCancel(noneApi) loadAndCancel(noneApi)
} else { } else {
val apiRandom = validAPIs.random() val apiRandom = validAPIs.random()

View file

@ -27,7 +27,6 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageLis
import com.lagradost.cloudstream3.ui.home.ParentItemAdapter import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
import com.lagradost.cloudstream3.ui.search.SearchHelper import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.ui.search.SearchViewModel import com.lagradost.cloudstream3.ui.search.SearchViewModel
import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper
@ -173,7 +172,7 @@ class QuickSearchFragment : Fragment() {
updateList(list.map { ongoing -> updateList(list.map { ongoing ->
val ongoingList = HomePageList( val ongoingList = HomePageList(
ongoing.apiName, ongoing.apiName,
if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList() if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList()
) )
ongoingList ongoingList
}) })

View file

@ -27,6 +27,7 @@ class SearchAdapter(
private val resView: AutofitRecyclerView, private val resView: AutofitRecyclerView,
private val clickCallback: (SearchClickCallback) -> Unit, private val clickCallback: (SearchClickCallback) -> Unit,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var hasNext : Boolean = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = if(parent.context.IsBottomLayout()) R.layout.search_result_grid_expanded else R.layout.search_result_grid val layout = if(parent.context.IsBottomLayout()) R.layout.search_result_grid_expanded else R.layout.search_result_grid

View file

@ -426,7 +426,7 @@ class SearchFragment : Fragment() {
val newItems = list.map { ongoing -> val newItems = list.map { ongoing ->
val ongoingList = HomePageList( val ongoingList = HomePageList(
ongoing.apiName, ongoing.apiName,
if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList() if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList()
) )
ongoingList ongoingList
} }

View file

@ -43,6 +43,7 @@ class SearchViewModel : ViewModel() {
_currentSearch.postValue(emptyList()) _currentSearch.postValue(emptyList())
} }
private var currentSearchIndex = 0
private var onGoingSearch: Job? = null private var onGoingSearch: Job? = null
fun searchAndCancel( fun searchAndCancel(
query: String, query: String,
@ -50,6 +51,7 @@ class SearchViewModel : ViewModel() {
ignoreSettings: Boolean = false, ignoreSettings: Boolean = false,
isQuickSearch: Boolean = false, isQuickSearch: Boolean = false,
) { ) {
currentSearchIndex++
onGoingSearch?.cancel() onGoingSearch?.cancel()
onGoingSearch = search(query, providersActive, ignoreSettings, isQuickSearch) onGoingSearch = search(query, providersActive, ignoreSettings, isQuickSearch)
} }
@ -70,6 +72,7 @@ class SearchViewModel : ViewModel() {
isQuickSearch: Boolean = false, isQuickSearch: Boolean = false,
) = ) =
viewModelScope.launch { viewModelScope.launch {
val currentIndex = currentSearchIndex
if (query.length <= 1) { if (query.length <= 1) {
clearSearch() clearSearch()
return@launch return@launch
@ -91,40 +94,44 @@ class SearchViewModel : ViewModel() {
_searchResponse.postValue(Resource.Loading()) _searchResponse.postValue(Resource.Loading())
val currentList = ArrayList<OnGoingSearch>()
_currentSearch.postValue(ArrayList()) _currentSearch.postValue(ArrayList())
withContext(Dispatchers.IO) { // This interrupts UI otherwise withContext(Dispatchers.IO) { // This interrupts UI otherwise
val currentList = ArrayList<OnGoingSearch>()
repos.filter { a -> repos.filter { a ->
(ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name))) && (!isQuickSearch || a.hasQuickSearch) (ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name))) && (!isQuickSearch || a.hasQuickSearch)
}.apmap { a -> // Parallel }.apmap { a -> // Parallel
val search = if (isQuickSearch) a.quickSearch(query) else a.search(query) val search = if (isQuickSearch) a.quickSearch(query) else a.search(query)
if(currentSearchIndex != currentIndex) return@apmap
currentList.add(OnGoingSearch(a.name, search)) currentList.add(OnGoingSearch(a.name, search))
_currentSearch.postValue(currentList) _currentSearch.postValue(currentList)
} }
}
_currentSearch.postValue(currentList)
val list = ArrayList<SearchResponse>() if(currentSearchIndex != currentIndex) return@withContext // this should prevent rewrite of existing data bug
val nestedList =
currentList.map { it.data }
.filterIsInstance<Resource.Success<List<SearchResponse>>>().map { it.value }
// I do it this way to move the relevant search results to the top _currentSearch.postValue(currentList)
var index = 0 val list = ArrayList<SearchResponse>()
while (true) { val nestedList =
var added = 0 currentList.map { it.data }
for (sublist in nestedList) { .filterIsInstance<Resource.Success<List<SearchResponse>>>().map { it.value }
if (sublist.size > index) {
list.add(sublist[index]) // I do it this way to move the relevant search results to the top
added++ var index = 0
while (true) {
var added = 0
for (sublist in nestedList) {
if (sublist.size > index) {
list.add(sublist[index])
added++
}
} }
if (added == 0) break
index++
} }
if (added == 0) break
index++
}
_searchResponse.postValue(Resource.Success(list)) _searchResponse.postValue(Resource.Success(list))
}
} }
} }

View file

@ -26,6 +26,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.core.text.toSpanned import androidx.core.text.toSpanned
import androidx.recyclerview.widget.RecyclerView
import androidx.tvprovider.media.tv.PreviewChannelHelper import androidx.tvprovider.media.tv.PreviewChannelHelper
import androidx.tvprovider.media.tv.TvContractCompat import androidx.tvprovider.media.tv.TvContractCompat
import androidx.tvprovider.media.tv.WatchNextProgram import androidx.tvprovider.media.tv.WatchNextProgram
@ -52,6 +53,11 @@ import java.net.URL
import java.net.URLDecoder import java.net.URLDecoder
object AppUtils { object AppUtils {
fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
for (i in 0..maxViewTypeId)
recycledViewPool.setMaxRecycledViews(i, maxPoolSize)
}
//fun Context.deleteFavorite(data: SearchResponse) { //fun Context.deleteFavorite(data: SearchResponse) {
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
// normalSafeApiCall { // normalSafeApiCall {

View file

@ -367,8 +367,8 @@
<string name="actor_background">Pomocniczy</string> <string name="actor_background">Pomocniczy</string>
<string name="home_source">Źródło</string> <string name="home_source">Źródło</string>
<string name="home_random">Losowy</string> <string name="home_random">Losowy</string>
<string name="coming_soon">Już wkrótce…</string> <string name="coming_soon">Już wkrótce…</string>
<string name="quality_cam">Cam</string> <string name="quality_cam">Cam</string>
<string name="quality_cam_rip">Cam</string> <string name="quality_cam_rip">Cam</string>
@ -391,4 +391,11 @@
<string name="error">Błąd</string> <string name="error">Błąd</string>
<string name="subtitles_remove_captions">Usuń informacje dla niesłyszących z napisów</string> <string name="subtitles_remove_captions">Usuń informacje dla niesłyszących z napisów</string>
<string name="subtitles_remove_bloat">Usuń nadmiarowe informacje z napisów</string> <string name="subtitles_remove_bloat">Usuń nadmiarowe informacje z napisów</string>
<string name="next">Dalej</string>
<string name="provider_languages_tip">Wyświetlaj filmy w wybranych językach</string>
<string name="previous">Cofnij</string>
<string name="skip_setup">Pomiń</string>
<string name="app_layout_subtext">Dostosuj wygląd aplikacji do urządzenia</string>
<string name="preferred_media_subtext">Preferowany rodzaj filmów</string>
<string name="setup_done">Gotowe</string>
</resources> </resources>

View file

@ -1,17 +1,17 @@
<!--https://newbedev.com/concatenate-multiple-strings-in-xml--> <!--https://newbedev.com/concatenate-multiple-strings-in-xml-->
<resources> <resources>
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG --> <!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
<string name="extra_info_format" translatable="false" formatted="true">%d %s | %sMB</string> <string name="extra_info_format" formatted="true" translatable="false">%d %s | %sMB</string>
<string name="storage_size_format" translatable="false" formatted="true">%s • %sGB</string> <string name="storage_size_format" formatted="true" translatable="false">%s • %sGB</string>
<string name="download_size_format" translatable="false" formatted="true">%sMB / %sMB</string> <string name="download_size_format" formatted="true" translatable="false">%sMB / %sMB</string>
<string name="mb_format" translatable="false" formatted="true">%dMB</string> <string name="mb_format" formatted="true" translatable="false">%dMB</string>
<string name="episode_name_format" translatable="false" formatted="true">%s %s</string> <string name="episode_name_format" formatted="true" translatable="false">%s %s</string>
<string name="ffw_text_format" translatable="false" formatted="true">+%d</string> <string name="ffw_text_format" formatted="true" translatable="false">+%d</string>
<string name="rew_text_format" translatable="false" formatted="true">-%d</string> <string name="rew_text_format" formatted="true" translatable="false">-%d</string>
<string name="ffw_text_regular_format" translatable="false" formatted="true">%d</string> <string name="ffw_text_regular_format" formatted="true" translatable="false">%d</string>
<string name="rew_text_regular_format" translatable="false" formatted="true">%d</string> <string name="rew_text_regular_format" formatted="true" translatable="false">%d</string>
<string name="rating_format" translatable="false" formatted="true">%.1f/10.0</string> <string name="rating_format" formatted="true" translatable="false">%.1f/10.0</string>
<string name="year_format" translatable="false" formatted="true">%d</string> <string name="year_format" formatted="true" translatable="false">%d</string>
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string> <string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
<string name="cast_format" formatted="true">Cast: %s</string> <string name="cast_format" formatted="true">Cast: %s</string>
<string name="next_episode_format" formatted="true">Bölüm %d şu tarihte yayınlanacak: </string> <string name="next_episode_format" formatted="true">Bölüm %d şu tarihte yayınlanacak: </string>
@ -39,6 +39,7 @@
<string name="duration_format" formatted="true">%d dakika</string> <string name="duration_format" formatted="true">%d dakika</string>
<string name="app_name">CloudStream</string> <string name="app_name">CloudStream</string>
<string name="play_with_app_name">CloudStream ile oynat</string>
<string name="title_home">Ana sayfa</string> <string name="title_home">Ana sayfa</string>
<string name="title_search">Arama</string> <string name="title_search">Arama</string>
<string name="title_downloads">İndirilenler</string> <string name="title_downloads">İndirilenler</string>
@ -66,6 +67,7 @@
<string name="type_re_watching">Yeniden izleniyor</string> <string name="type_re_watching">Yeniden izleniyor</string>
<string name="play_movie_button">Filmi oynat</string> <string name="play_movie_button">Filmi oynat</string>
<string name="play_livestream_button">Canlı yayını oynat</string>
<string name="play_torrent_button">Torrent oynat</string> <string name="play_torrent_button">Torrent oynat</string>
<string name="pick_source">Kaynaklar</string> <string name="pick_source">Kaynaklar</string>
<string name="pick_subtitle">Alt yazılar</string> <string name="pick_subtitle">Alt yazılar</string>
@ -268,6 +270,7 @@
<string name="documentaries">Belgeseller</string> <string name="documentaries">Belgeseller</string>
<string name="ova">OVA</string> <string name="ova">OVA</string>
<string name="asian_drama">Asya dramaları</string> <string name="asian_drama">Asya dramaları</string>
<string name="livestreams">Canlı yayınlar</string>
<!--singular--> <!--singular-->
<string name="movies_singular">Film</string> <string name="movies_singular">Film</string>
@ -278,6 +281,7 @@
<string name="torrent_singular">Torrent</string> <string name="torrent_singular">Torrent</string>
<string name="documentaries_singular">Belgesel</string> <string name="documentaries_singular">Belgesel</string>
<string name="asian_drama_singular">Asya draması</string> <string name="asian_drama_singular">Asya draması</string>
<string name="live_singular">Canlı yayın</string>
<string name="source_error">Kaynak hatası</string> <string name="source_error">Kaynak hatası</string>
<string name="remote_error">Sunucu hatası</string> <string name="remote_error">Sunucu hatası</string>
@ -495,5 +499,15 @@
<string name="trailer">Fragman</string> <string name="trailer">Fragman</string>
<string name="network_adress_example">Yayına bağlan</string> <string name="network_adress_example">Yayına bağlan</string>
<string name="referer">Yönlendiren</string> <string name="referer">Yönlendiren</string>
<string name="next">İleri</string>
<string name="provider_languages_tip">Videoları bu dillerde izle</string>
<string name="previous">Geri</string>
<string name="skip_setup">Kurulumu atla</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Merhaba boş parça</string>
<string name="app_layout_subtext">Cihazınıza uygun görünümü seçin</string>
<string name="crash_reporting_title">Çökme raporları</string>
<string name="preferred_media_subtext">Ne izlemek istiyorsunuz?</string>
<string name="setup_done">Bitti</string>
</resources> </resources>