forked from recloudstream/cloudstream
homepage somewhat working
This commit is contained in:
parent
f270f9f551
commit
61323b5c56
15 changed files with 545 additions and 51 deletions
|
@ -47,6 +47,13 @@ object APIHolder {
|
||||||
return apis[defProvider]
|
return apis[defProvider]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getApiFromNameNull(apiName: String?): MainAPI? {
|
||||||
|
for (api in apis) {
|
||||||
|
if (apiName == api.name)
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
fun LoadResponse.getId(): Int {
|
fun LoadResponse.getId(): Int {
|
||||||
return url.replace(getApiFromName(apiName).mainUrl, "").hashCode()
|
return url.replace(getApiFromName(apiName).mainUrl, "").hashCode()
|
||||||
|
@ -70,14 +77,19 @@ abstract class MainAPI {
|
||||||
/**If link is stored in the "data" string, so links can be instantly loaded*/
|
/**If link is stored in the "data" string, so links can be instantly loaded*/
|
||||||
open val instantLinkLoading = false
|
open val instantLinkLoading = false
|
||||||
|
|
||||||
open val hasQuickSearch = false
|
|
||||||
|
|
||||||
/**Set false if links require referer or for some reason cant be played on a chromecast*/
|
/**Set false if links require referer or for some reason cant be played on a chromecast*/
|
||||||
open val hasChromecastSupport = true
|
open val hasChromecastSupport = true
|
||||||
|
|
||||||
/**If all links are m3u8 then set this to false*/
|
/**If all links are m3u8 then set this to false*/
|
||||||
open val hasDownloadSupport = true
|
open val hasDownloadSupport = true
|
||||||
|
|
||||||
|
open val hasMainPage = false
|
||||||
|
open val hasQuickSearch = false
|
||||||
|
|
||||||
|
open fun getMainPage() : HomePageResponse? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
open fun search(query: String): ArrayList<SearchResponse>? {
|
open fun search(query: String): ArrayList<SearchResponse>? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -161,6 +173,15 @@ fun TvType.isMovieType(): Boolean {
|
||||||
|
|
||||||
data class SubtitleFile(val lang: String, val url: String)
|
data class SubtitleFile(val lang: String, val url: String)
|
||||||
|
|
||||||
|
class HomePageResponse(
|
||||||
|
val items: List<HomePageList>
|
||||||
|
)
|
||||||
|
|
||||||
|
class HomePageList(
|
||||||
|
val name: String,
|
||||||
|
val list: List<SearchResponse>
|
||||||
|
)
|
||||||
|
|
||||||
interface SearchResponse {
|
interface SearchResponse {
|
||||||
val name: String
|
val name: String
|
||||||
val url: String // PUBLIC URL FOR OPEN IN APP
|
val url: String // PUBLIC URL FOR OPEN IN APP
|
||||||
|
|
|
@ -9,6 +9,7 @@ import java.net.URLEncoder
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
const val SHIRO_TIMEOUT_TIME = 60.0
|
||||||
|
|
||||||
class ShiroProvider : MainAPI() {
|
class ShiroProvider : MainAPI() {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -55,6 +56,9 @@ class ShiroProvider : MainAPI() {
|
||||||
override val hasQuickSearch: Boolean
|
override val hasQuickSearch: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
|
override val hasMainPage: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
data class ShiroSearchResponseShow(
|
data class ShiroSearchResponseShow(
|
||||||
@JsonProperty("image") val image: String,
|
@JsonProperty("image") val image: String,
|
||||||
@JsonProperty("_id") val _id: String,
|
@JsonProperty("_id") val _id: String,
|
||||||
|
@ -134,6 +138,46 @@ class ShiroProvider : MainAPI() {
|
||||||
@JsonProperty("status") val status: String,
|
@JsonProperty("status") val status: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class ShiroHomePageData(
|
||||||
|
@JsonProperty("trending_animes") val trending_animes: List<AnimePageData>,
|
||||||
|
@JsonProperty("ongoing_animes") val ongoing_animes: List<AnimePageData>,
|
||||||
|
@JsonProperty("latest_animes") val latest_animes: List<AnimePageData>,
|
||||||
|
@JsonProperty("latest_episodes") val latest_episodes: List<ShiroEpisodes>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ShiroHomePage(
|
||||||
|
@JsonProperty("status") val status: String,
|
||||||
|
@JsonProperty("data") val data: ShiroHomePageData,
|
||||||
|
@JsonProperty("random") var random: AnimePage?,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun toHomePageList(list: List<AnimePageData>, name: String): HomePageList {
|
||||||
|
return HomePageList(name, list.map { data ->
|
||||||
|
val type = getType(data.type)
|
||||||
|
val isDubbed =
|
||||||
|
data.language == "dubbed"
|
||||||
|
|
||||||
|
val set: EnumSet<DubStatus> =
|
||||||
|
EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
|
||||||
|
|
||||||
|
val episodeCount = data.episodeCount?.toIntOrNull()
|
||||||
|
|
||||||
|
return@map AnimeSearchResponse(
|
||||||
|
data.name.replace("Dubbed", ""), // i.english ?: i.canonicalTitle,
|
||||||
|
"$mainUrl/anime/${data.slug}",
|
||||||
|
data.slug,
|
||||||
|
this.name,
|
||||||
|
type,
|
||||||
|
"https://cdn.shiro.is/${data.image}",
|
||||||
|
data.year?.toIntOrNull(),
|
||||||
|
data.canonicalTitle,
|
||||||
|
set,
|
||||||
|
if (isDubbed) episodeCount else null,
|
||||||
|
if (!isDubbed) episodeCount else null,
|
||||||
|
)
|
||||||
|
}.toList())
|
||||||
|
}
|
||||||
|
|
||||||
private fun turnSearchIntoResponse(data: ShiroSearchResponseShow): AnimeSearchResponse {
|
private fun turnSearchIntoResponse(data: ShiroSearchResponseShow): AnimeSearchResponse {
|
||||||
val type = getType(data.type)
|
val type = getType(data.type)
|
||||||
val isDubbed =
|
val isDubbed =
|
||||||
|
@ -141,12 +185,9 @@ class ShiroProvider : MainAPI() {
|
||||||
data.language == "dubbed"
|
data.language == "dubbed"
|
||||||
else
|
else
|
||||||
data.slug.contains("dubbed")
|
data.slug.contains("dubbed")
|
||||||
val set: EnumSet<DubStatus> = EnumSet.noneOf(DubStatus::class.java)
|
val set: EnumSet<DubStatus> =
|
||||||
|
EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
|
||||||
|
|
||||||
if (isDubbed)
|
|
||||||
set.add(DubStatus.Dubbed)
|
|
||||||
else
|
|
||||||
set.add(DubStatus.Subbed)
|
|
||||||
val episodeCount = data.episodeCount?.toIntOrNull()
|
val episodeCount = data.episodeCount?.toIntOrNull()
|
||||||
|
|
||||||
return AnimeSearchResponse(
|
return AnimeSearchResponse(
|
||||||
|
@ -164,7 +205,26 @@ class ShiroProvider : MainAPI() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun quickSearch(query: String): ArrayList<SearchResponse> {
|
override fun getMainPage(): HomePageResponse? {
|
||||||
|
if (!autoLoadToken()) return null
|
||||||
|
|
||||||
|
val url = "https://tapi.shiro.is/latest?token=$token"
|
||||||
|
val response = khttp.get(url, timeout = SHIRO_TIMEOUT_TIME)
|
||||||
|
val res = response.text.let { mapper.readValue<ShiroHomePage>(it) }
|
||||||
|
|
||||||
|
val d = res.data
|
||||||
|
return HomePageResponse(
|
||||||
|
listOf(
|
||||||
|
toHomePageList(d.trending_animes, "Trending"),
|
||||||
|
toHomePageList(d.ongoing_animes, "Ongoing"),
|
||||||
|
toHomePageList(d.latest_animes, "Latest")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun quickSearch(query: String): ArrayList<SearchResponse>? {
|
||||||
|
if (!autoLoadToken()) return null
|
||||||
|
|
||||||
val returnValue: ArrayList<SearchResponse> = ArrayList()
|
val returnValue: ArrayList<SearchResponse> = ArrayList()
|
||||||
|
|
||||||
val response = khttp.get(
|
val response = khttp.get(
|
||||||
|
@ -214,8 +274,8 @@ class ShiroProvider : MainAPI() {
|
||||||
val episodes =
|
val episodes =
|
||||||
ArrayList<AnimeEpisode>(
|
ArrayList<AnimeEpisode>(
|
||||||
data.episodes?.distinctBy { it.episode_number }?.sortedBy { it.episode_number }
|
data.episodes?.distinctBy { it.episode_number }?.sortedBy { it.episode_number }
|
||||||
?.map { AnimeEpisode(it.videos[0].video_id) }
|
?.map { AnimeEpisode(it.videos[0].video_id) }
|
||||||
?: ArrayList<AnimeEpisode>())
|
?: ArrayList<AnimeEpisode>())
|
||||||
val status = when (data.status) {
|
val status = when (data.status) {
|
||||||
"current" -> ShowStatus.Ongoing
|
"current" -> ShowStatus.Ongoing
|
||||||
"finished" -> ShowStatus.Completed
|
"finished" -> ShowStatus.Completed
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.bumptech.glide.load.HttpException
|
import com.bumptech.glide.load.HttpException
|
||||||
|
import com.lagradost.cloudstream3.ui.ErrorLoadingException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
|
@ -67,6 +68,9 @@ suspend fun <T> safeApiCall(
|
||||||
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 -> {
|
||||||
|
Resource.Failure(true, null, null, "Error loading, try again later.")
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val stackTraceMsg = throwable.localizedMessage + "\n\n" + throwable.stackTrace.joinToString(
|
val stackTraceMsg = throwable.localizedMessage + "\n\n" + throwable.stackTrace.joinToString(
|
||||||
separator = "\n"
|
separator = "\n"
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.lagradost.cloudstream3.ui
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
|
||||||
|
class ErrorLoadingException(message: String) : Exception(message)
|
||||||
|
|
||||||
|
class APIRepository(val api: MainAPI) {
|
||||||
|
val name : String get() = api.name
|
||||||
|
val mainUrl : String get() = api.mainUrl
|
||||||
|
|
||||||
|
suspend fun load(url: String): Resource<LoadResponse> {
|
||||||
|
return safeApiCall {
|
||||||
|
api.load(url) ?: throw ErrorLoadingException("Error Loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun search(query: String): Resource<ArrayList<SearchResponse>> {
|
||||||
|
return safeApiCall {
|
||||||
|
api.search(query) ?: throw ErrorLoadingException("Error Loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun quickSearch(query: String): Resource<ArrayList<SearchResponse>> {
|
||||||
|
return safeApiCall {
|
||||||
|
api.quickSearch(query) ?: throw ErrorLoadingException("Error Loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMainPage(): Resource<HomePageResponse> {
|
||||||
|
return safeApiCall {
|
||||||
|
api.getMainPage() ?: throw ErrorLoadingException("Error Loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadLinks(
|
||||||
|
data: String,
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
return normalSafeApiCall { api.loadLinks(data, isCasting, subtitleCallback, callback) } ?: false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.home
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.cardview.widget.CardView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import kotlinx.android.synthetic.main.home_result_grid.view.*
|
||||||
|
|
||||||
|
class HomeChildItemAdapter(
|
||||||
|
var cardList: List<Any>,
|
||||||
|
private val clickCallback: (SearchResponse) -> Unit
|
||||||
|
) :
|
||||||
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val layout = R.layout.home_result_grid
|
||||||
|
return CardViewHolder(
|
||||||
|
LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is CardViewHolder -> {
|
||||||
|
holder.bind(cardList[position])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return cardList.size
|
||||||
|
}
|
||||||
|
|
||||||
|
class CardViewHolder
|
||||||
|
constructor(itemView: View, private val clickCallback: (SearchResponse) -> Unit) :
|
||||||
|
RecyclerView.ViewHolder(itemView) {
|
||||||
|
val cardView: ImageView = itemView.imageView
|
||||||
|
private val cardText: TextView = itemView.imageText
|
||||||
|
private val textType: TextView? = itemView.text_type
|
||||||
|
// val search_result_lang: ImageView? = itemView.search_result_lang
|
||||||
|
|
||||||
|
private val textIsDub: View? = itemView.text_is_dub
|
||||||
|
private val textIsSub: View? = itemView.text_is_sub
|
||||||
|
|
||||||
|
//val cardTextExtra: TextView? = itemView.imageTextExtra
|
||||||
|
//val imageTextProvider: TextView? = itemView.imageTextProvider
|
||||||
|
private val bg: CardView = itemView.backgroundCard
|
||||||
|
|
||||||
|
fun bind(card: Any) {
|
||||||
|
if (card is SearchResponse) { // GENERIC
|
||||||
|
|
||||||
|
textType?.text = when (card.type) {
|
||||||
|
TvType.Anime -> "Anime"
|
||||||
|
TvType.Movie -> "Movie"
|
||||||
|
TvType.ONA -> "ONA"
|
||||||
|
TvType.TvSeries -> "TV"
|
||||||
|
}
|
||||||
|
// search_result_lang?.visibility = View.GONE
|
||||||
|
|
||||||
|
textIsDub?.visibility = View.GONE
|
||||||
|
textIsSub?.visibility = View.GONE
|
||||||
|
|
||||||
|
cardText.text = card.name
|
||||||
|
|
||||||
|
//imageTextProvider.text = card.apiName
|
||||||
|
if (!card.posterUrl.isNullOrEmpty()) {
|
||||||
|
|
||||||
|
val glideUrl =
|
||||||
|
GlideUrl(card.posterUrl)
|
||||||
|
|
||||||
|
Glide.with(cardView.context)
|
||||||
|
.load(glideUrl)
|
||||||
|
.into(cardView)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bg.setOnClickListener {
|
||||||
|
clickCallback.invoke(card)
|
||||||
|
// (activity as AppCompatActivity).loadResult(card.url, card.slug, card.apiName)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (card) {
|
||||||
|
is AnimeSearchResponse -> {
|
||||||
|
if (card.dubStatus?.size == 1) {
|
||||||
|
//search_result_lang?.visibility = View.VISIBLE
|
||||||
|
if (card.dubStatus.contains(DubStatus.Dubbed)) {
|
||||||
|
textIsDub?.visibility = View.VISIBLE
|
||||||
|
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.dubColor))
|
||||||
|
} else if (card.dubStatus.contains(DubStatus.Subbed)) {
|
||||||
|
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.subColor))
|
||||||
|
textIsSub?.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,14 +4,20 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
|
import com.lagradost.cloudstream3.utils.HOMEPAGE_API
|
||||||
|
import kotlinx.android.synthetic.main.fragment_child_downloads.*
|
||||||
|
import kotlinx.android.synthetic.main.fragment_home.*
|
||||||
|
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var homeViewModel: HomeViewModel
|
private lateinit var homeViewModel: HomeViewModel
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
|
@ -21,11 +27,40 @@ class HomeFragment : Fragment() {
|
||||||
): View? {
|
): View? {
|
||||||
homeViewModel =
|
homeViewModel =
|
||||||
ViewModelProvider(this).get(HomeViewModel::class.java)
|
ViewModelProvider(this).get(HomeViewModel::class.java)
|
||||||
val root = inflater.inflate(R.layout.fragment_home, container, false)
|
|
||||||
val textView: TextView = root.findViewById(R.id.text_home)
|
return inflater.inflate(R.layout.fragment_home, container, false)
|
||||||
homeViewModel.text.observe(viewLifecycleOwner, Observer {
|
}
|
||||||
textView.text = it
|
|
||||||
})
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
return root
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
observe(homeViewModel.apiName) {
|
||||||
|
context?.setKey(HOMEPAGE_API, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
observe(homeViewModel.page) {
|
||||||
|
when (it) {
|
||||||
|
is Resource.Success -> {
|
||||||
|
val d = it.value
|
||||||
|
(home_master_recycler?.adapter as ParentItemAdapter?)?.itemList = d.items
|
||||||
|
home_master_recycler?.adapter?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
is Resource.Failure -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
is Resource.Loading -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = ParentItemAdapter(listOf()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
home_master_recycler.adapter = adapter
|
||||||
|
home_master_recycler.layoutManager = GridLayoutManager(context, 1)
|
||||||
|
|
||||||
|
homeViewModel.load(context?.getKey(HOMEPAGE_API))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.home
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.lagradost.cloudstream3.HomePageList
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
|
import kotlinx.android.synthetic.main.fragment_home.*
|
||||||
|
import kotlinx.android.synthetic.main.homepage_parent.view.*
|
||||||
|
|
||||||
|
class ParentItemAdapter(
|
||||||
|
var itemList: List<HomePageList>,
|
||||||
|
private val clickCallback: (SearchResponse) -> Unit
|
||||||
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, i: Int): ParentViewHolder {
|
||||||
|
val layout = R.layout.homepage_parent
|
||||||
|
return ParentViewHolder(
|
||||||
|
LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is ParentViewHolder -> {
|
||||||
|
holder.bind(itemList[position])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return itemList.size
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParentViewHolder
|
||||||
|
constructor(itemView: View, private val clickCallback: (SearchResponse) -> Unit) :
|
||||||
|
RecyclerView.ViewHolder(itemView) {
|
||||||
|
val title: TextView = itemView.home_parent_item_title
|
||||||
|
val recyclerView: RecyclerView = itemView.home_child_recyclerview
|
||||||
|
fun bind(info: HomePageList) {
|
||||||
|
title.text = info.name
|
||||||
|
recyclerView.adapter = HomeChildItemAdapter(info.list, clickCallback)
|
||||||
|
recyclerView.layoutManager = GridLayoutManager(itemView.context, 1)
|
||||||
|
(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,11 +3,35 @@ package com.lagradost.cloudstream3.ui.home
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.lagradost.cloudstream3.APIHolder.apis
|
||||||
|
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||||
|
import com.lagradost.cloudstream3.HomePageResponse
|
||||||
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class HomeViewModel : ViewModel() {
|
class HomeViewModel : ViewModel() {
|
||||||
|
var repo: APIRepository? = null
|
||||||
|
|
||||||
private val _text = MutableLiveData<String>().apply {
|
private val _apiName = MutableLiveData<String>()
|
||||||
value = "This is home Fragment"
|
val apiName: LiveData<String> = _apiName
|
||||||
|
|
||||||
|
private val _page = MutableLiveData<Resource<HomePageResponse>>()
|
||||||
|
val page: LiveData<Resource<HomePageResponse>> = _page
|
||||||
|
|
||||||
|
private fun autoloadRepo(): APIRepository {
|
||||||
|
return APIRepository(apis.first { it.hasMainPage })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(preferredApiName: String?) = viewModelScope.launch {
|
||||||
|
val api = getApiFromNameNull(preferredApiName)
|
||||||
|
repo = if (api?.hasMainPage == true) {
|
||||||
|
APIRepository(api)
|
||||||
|
} else {
|
||||||
|
autoloadRepo()
|
||||||
|
}
|
||||||
|
_page.postValue(Resource.Loading())
|
||||||
|
_page.postValue(repo?.getMainPage())
|
||||||
}
|
}
|
||||||
val text: LiveData<String> = _text
|
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
||||||
import com.lagradost.cloudstream3.APIHolder.getId
|
import com.lagradost.cloudstream3.APIHolder.getId
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
||||||
|
@ -20,6 +21,8 @@ const val EPISODE_RANGE_SIZE = 50
|
||||||
const val EPISODE_RANGE_OVERLOAD = 60
|
const val EPISODE_RANGE_OVERLOAD = 60
|
||||||
|
|
||||||
class ResultViewModel : ViewModel() {
|
class ResultViewModel : ViewModel() {
|
||||||
|
var repo : APIRepository? = null
|
||||||
|
|
||||||
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
|
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
|
||||||
private val _episodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
|
private val _episodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
|
||||||
private val _publicEpisodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
|
private val _publicEpisodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
|
||||||
|
@ -137,19 +140,14 @@ class ResultViewModel : ViewModel() {
|
||||||
updateEpisodes(context, null, copy, selectedSeason.value)
|
updateEpisodes(context, null, copy, selectedSeason.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// THIS SHOULD AT LEAST CLEAN IT UP, SO APIS CAN SWITCH DOMAIN
|
|
||||||
private fun getId(url: String, api: MainAPI): Int {
|
|
||||||
return url.replace(api.mainUrl, "").hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun load(context: Context, url: String, apiName: String) = viewModelScope.launch {
|
fun load(context: Context, url: String, apiName: String) = viewModelScope.launch {
|
||||||
_resultResponse.postValue(Resource.Loading(url))
|
_resultResponse.postValue(Resource.Loading(url))
|
||||||
|
|
||||||
_apiName.postValue(apiName)
|
_apiName.postValue(apiName)
|
||||||
val api = getApiFromName(apiName)
|
val api = getApiFromName(apiName)
|
||||||
val data = safeApiCall {
|
repo = APIRepository(api)
|
||||||
api.load(url)
|
|
||||||
}
|
val data = repo?.load(url)
|
||||||
|
|
||||||
_resultResponse.postValue(data)
|
_resultResponse.postValue(data)
|
||||||
|
|
||||||
|
@ -278,7 +276,7 @@ class ResultViewModel : ViewModel() {
|
||||||
val links = ArrayList<ExtractorLink>()
|
val links = ArrayList<ExtractorLink>()
|
||||||
val subs = ArrayList<SubtitleFile>()
|
val subs = ArrayList<SubtitleFile>()
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
getApiFromName(_apiName.value).loadLinks(data, isCasting, { subtitleFile ->
|
repo?.loadLinks(data, isCasting, { subtitleFile ->
|
||||||
if (!subs.any { it.url == subtitleFile.url }) {
|
if (!subs.any { it.url == subtitleFile.url }) {
|
||||||
subs.add(subtitleFile)
|
subs.add(subtitleFile)
|
||||||
_allEpisodesSubs.value?.set(id, subs)
|
_allEpisodesSubs.value?.set(id, subs)
|
||||||
|
|
|
@ -7,12 +7,14 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.lagradost.cloudstream3.APIHolder.allApi
|
import com.lagradost.cloudstream3.APIHolder.allApi
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SearchViewModel : ViewModel() {
|
class SearchViewModel : ViewModel() {
|
||||||
private val _searchResponse: MutableLiveData<Resource<ArrayList<Any>>> = MutableLiveData()
|
private val _searchResponse: MutableLiveData<Resource<ArrayList<Any>>> = MutableLiveData()
|
||||||
val searchResponse: LiveData<Resource<ArrayList<Any>>> get() = _searchResponse
|
val searchResponse: LiveData<Resource<ArrayList<Any>>> get() = _searchResponse
|
||||||
var searchCounter = 0
|
var searchCounter = 0
|
||||||
|
private val repo = APIRepository(allApi)
|
||||||
|
|
||||||
private fun clearSearch() {
|
private fun clearSearch() {
|
||||||
_searchResponse.postValue(Resource.Success(ArrayList()))
|
_searchResponse.postValue(Resource.Success(ArrayList()))
|
||||||
|
@ -26,9 +28,8 @@ class SearchViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
val localSearchCounter = searchCounter
|
val localSearchCounter = searchCounter
|
||||||
_searchResponse.postValue(Resource.Loading())
|
_searchResponse.postValue(Resource.Loading())
|
||||||
val data = safeApiCall {
|
val data = repo.search(query)
|
||||||
allApi.search(query)
|
|
||||||
}
|
|
||||||
if(localSearchCounter != searchCounter) return@launch
|
if(localSearchCounter != searchCounter) return@launch
|
||||||
_searchResponse.postValue(data as Resource<ArrayList<Any>>?)
|
_searchResponse.postValue(data as Resource<ArrayList<Any>>?)
|
||||||
}
|
}
|
||||||
|
@ -41,9 +42,7 @@ class SearchViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
val localSearchCounter = searchCounter
|
val localSearchCounter = searchCounter
|
||||||
_searchResponse.postValue(Resource.Loading())
|
_searchResponse.postValue(Resource.Loading())
|
||||||
val data = safeApiCall {
|
val data = repo.quickSearch(query)
|
||||||
allApi.quickSearch(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(localSearchCounter != searchCounter) return@launch
|
if(localSearchCounter != searchCounter) return@launch
|
||||||
_searchResponse.postValue(data as Resource<ArrayList<Any>>?)
|
_searchResponse.postValue(data as Resource<ArrayList<Any>>?)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
|
const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
|
||||||
const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache"
|
const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache"
|
||||||
const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha"
|
const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha"
|
||||||
|
const val HOMEPAGE_API = "home_api_used"
|
||||||
|
|
||||||
const val PREFERENCES_NAME: String = "rebuild_preference"
|
const val PREFERENCES_NAME: String = "rebuild_preference"
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,10 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.home.HomeFragment">
|
tools:context=".ui.home.HomeFragment">
|
||||||
|
|
||||||
<TextView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/text_home"
|
android:id="@+id/home_master_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginStart="8dp"
|
tools:listitem="@layout/homepage_parent"
|
||||||
android:layout_marginTop="8dp"
|
/>
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textSize="20sp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
125
app/src/main/res/layout/home_result_grid.xml
Normal file
125
app/src/main/res/layout/home_result_grid.xml
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<!-- android:layout_width="114dp"
|
||||||
|
android:layout_height="180dp"-->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:layout_margin="2dp"
|
||||||
|
android:layout_width="114dp"
|
||||||
|
android:layout_height="180dp"
|
||||||
|
android:layout_marginBottom="2dp"
|
||||||
|
android:elevation="10dp"
|
||||||
|
app:cardCornerRadius="@dimen/roundedImageRadius"
|
||||||
|
android:id="@+id/backgroundCard"
|
||||||
|
app:cardBackgroundColor="@color/darkBackground"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<ImageView
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
tools:src="@drawable/example_poster"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/search_poster_descript"/>
|
||||||
|
<ImageView
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="false"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:src="@drawable/title_shadow"
|
||||||
|
android:layout_gravity="bottom" android:contentDescription="@string/shadow_descript">
|
||||||
|
</ImageView>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:paddingBottom="5dp"
|
||||||
|
android:paddingTop="5dp"
|
||||||
|
android:textColor="@color/textColor"
|
||||||
|
android:id="@+id/imageText"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:paddingStart="5dp"
|
||||||
|
android:paddingEnd="5dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
/>
|
||||||
|
<TextView
|
||||||
|
android:text="Movie"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:id="@+id/text_type"
|
||||||
|
android:textColor="@color/textColor"
|
||||||
|
android:paddingRight="10dp"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:minWidth="50dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@drawable/type_bg_color"
|
||||||
|
android:layout_width="wrap_content" android:layout_height="wrap_content">
|
||||||
|
</TextView>
|
||||||
|
<!--<View
|
||||||
|
android:id="@+id/search_result_lang"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:alpha="0.9">
|
||||||
|
|
||||||
|
</View>-->
|
||||||
|
<!--<ImageView
|
||||||
|
android:src="@drawable/ic_baseline_bookmark_24"
|
||||||
|
android:id="@+id/search_result_lang"
|
||||||
|
android:layout_gravity="right"
|
||||||
|
android:layout_marginTop="-5dp"
|
||||||
|
android:layout_marginRight="-6.5dp"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp">
|
||||||
|
</ImageView>-->
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<ImageView android:id="@+id/text_is_dub" android:tint="@color/colorPrimary"
|
||||||
|
android:src="@drawable/ic_baseline_subtitles_24" android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp">
|
||||||
|
|
||||||
|
</ImageView>-->
|
||||||
|
<TextView
|
||||||
|
android:text="@string/app_dubbed_text"
|
||||||
|
android:id="@+id/text_is_dub"
|
||||||
|
android:textColor="@color/textColor"
|
||||||
|
android:paddingRight="10dp"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:minWidth="50dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@drawable/dub_bg_color"
|
||||||
|
android:layout_width="wrap_content" android:layout_height="wrap_content">
|
||||||
|
</TextView>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_is_sub"
|
||||||
|
android:text="@string/app_subbed_text"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:textColor="@color/textColor"
|
||||||
|
android:paddingRight="10dp"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:minWidth="50dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@drawable/sub_bg_color"
|
||||||
|
android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||||
|
>
|
||||||
|
</TextView>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
33
app/src/main/res/layout/homepage_parent.xml
Normal file
33
app/src/main/res/layout/homepage_parent.xml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@color/colorPrimary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/home_parent_item_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="12sp"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:text="Trending"
|
||||||
|
/>
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="230dp"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
>
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
android:id="@+id/home_child_recyclerview"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:listitem="@layout/home_result_grid"
|
||||||
|
/>
|
||||||
|
</RelativeLayout>
|
||||||
|
</LinearLayout>
|
|
@ -9,10 +9,8 @@
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:id="@+id/search_result_root"
|
android:id="@+id/search_result_root"
|
||||||
|
|
||||||
>
|
>
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
|
|
||||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
android:layout_margin="2dp"
|
android:layout_margin="2dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
Loading…
Reference in a new issue