mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
added https://aniflix.pro
This commit is contained in:
parent
7bf77cb408
commit
f7b23d6467
13 changed files with 370 additions and 27 deletions
|
@ -134,6 +134,7 @@ object APIHolder {
|
||||||
//MultiAnimeProvider(),
|
//MultiAnimeProvider(),
|
||||||
NginxProvider(),
|
NginxProvider(),
|
||||||
OlgplyProvider(),
|
OlgplyProvider(),
|
||||||
|
AniflixProvider(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,246 @@
|
||||||
|
package com.lagradost.cloudstream3.animeproviders
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
|
|
||||||
|
class AniflixProvider : MainAPI() {
|
||||||
|
override var mainUrl = "https://aniflix.pro"
|
||||||
|
override var name = "Aniflix"
|
||||||
|
override val hasMainPage = false
|
||||||
|
|
||||||
|
override val supportedTypes = setOf(
|
||||||
|
TvType.AnimeMovie,
|
||||||
|
TvType.OVA,
|
||||||
|
TvType.Anime,
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var token: String? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getToken(): String {
|
||||||
|
return token ?: run {
|
||||||
|
Regex("([^/]*)/_buildManifest\\.js").find(app.get(mainUrl).text)?.groupValues?.getOrNull(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
?.also {
|
||||||
|
token = it
|
||||||
|
}
|
||||||
|
?: throw ErrorLoadingException("No token found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Anime.toSearchResponse(): SearchResponse? {
|
||||||
|
return newAnimeSearchResponse(
|
||||||
|
title?.english ?: title?.romaji ?: return null,
|
||||||
|
"$mainUrl/anime/${id ?: return null}"
|
||||||
|
) {
|
||||||
|
posterUrl = coverImage?.large ?: coverImage?.medium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun search(query: String): List<SearchResponse>? {
|
||||||
|
val token = getToken()
|
||||||
|
val url = "$mainUrl/_next/data/$token/search.json?keyword=$query"
|
||||||
|
val response = app.get(url)
|
||||||
|
println("resp: $url ===> ${response.text}")
|
||||||
|
val searchResponse =
|
||||||
|
response.parsedSafe<Search>()
|
||||||
|
?: throw ErrorLoadingException("No Media")
|
||||||
|
return searchResponse.pageProps?.searchResults?.Page?.media?.mapNotNull { media ->
|
||||||
|
media.toSearchResponse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(url: String): LoadResponse {
|
||||||
|
val token = getToken()
|
||||||
|
val id = Regex("$mainUrl/anime/([0-9]*)").find(url)?.groupValues?.getOrNull(1)
|
||||||
|
?: throw ErrorLoadingException("Error parsing link for id")
|
||||||
|
|
||||||
|
val res = app.get("https://aniflix.pro/_next/data/$token/anime/$id.json?id=$id")
|
||||||
|
.parsedSafe<AnimeResponsePage>()?.pageProps
|
||||||
|
?: throw ErrorLoadingException("Invalid Json reponse")
|
||||||
|
val isMovie = res.anime.format == "MOVIE"
|
||||||
|
return newAnimeLoadResponse(
|
||||||
|
res.anime.title?.english ?: res.anime.title?.romaji
|
||||||
|
?: throw ErrorLoadingException("Invalid title reponse"),
|
||||||
|
url, if (isMovie) TvType.AnimeMovie else TvType.Anime
|
||||||
|
) {
|
||||||
|
recommendations = res.recommended.mapNotNull { it.toSearchResponse() }
|
||||||
|
tags = res.anime.genres
|
||||||
|
posterUrl = res.anime.coverImage?.large ?: res.anime.coverImage?.medium
|
||||||
|
plot = res.anime.description
|
||||||
|
showStatus = when (res.anime.status) {
|
||||||
|
"FINISHED" -> ShowStatus.Completed
|
||||||
|
"RELEASING" -> ShowStatus.Ongoing
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
addAniListId(id.toIntOrNull())
|
||||||
|
|
||||||
|
// subbed because they are both subbed and dubbed
|
||||||
|
if (isMovie)
|
||||||
|
addEpisodes(
|
||||||
|
DubStatus.Subbed,
|
||||||
|
listOf(newEpisode("$mainUrl/api/anime/?id=$id&episode=1"))
|
||||||
|
)
|
||||||
|
else
|
||||||
|
addEpisodes(DubStatus.Subbed, res.episodes.episodes?.nodes?.mapNotNull { node ->
|
||||||
|
val ep = node?.number ?: return@mapNotNull null
|
||||||
|
//"$mainUrl/_next/data/$token/watch/$id.json?episode=${node.number ?: return@mapNotNull null}&id=$id"
|
||||||
|
newEpisode("$mainUrl/api/anime?id=$id&episode=${ep}") {
|
||||||
|
episode = ep
|
||||||
|
posterUrl = node.thumbnail?.original?.url
|
||||||
|
name = node.titles?.canonical
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun loadLinks(
|
||||||
|
data: String,
|
||||||
|
isCasting: Boolean,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
): Boolean {
|
||||||
|
return app.get(data).parsed<AniLoadResponse>().let { res ->
|
||||||
|
val dubReferer = res.dub?.Referer ?: ""
|
||||||
|
res.dub?.sources?.forEach { source ->
|
||||||
|
callback(
|
||||||
|
ExtractorLink(
|
||||||
|
name,
|
||||||
|
"${source.label ?: name} (DUB)",
|
||||||
|
source.file ?: return@forEach,
|
||||||
|
dubReferer,
|
||||||
|
getQualityFromName(source.label),
|
||||||
|
source.type == "hls"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val subReferer = res.dub?.Referer ?: ""
|
||||||
|
res.sub?.sources?.forEach { source ->
|
||||||
|
callback(
|
||||||
|
ExtractorLink(
|
||||||
|
name,
|
||||||
|
"${source.label ?: name} (SUB)",
|
||||||
|
source.file ?: return@forEach,
|
||||||
|
subReferer,
|
||||||
|
getQualityFromName(source.label),
|
||||||
|
source.type == "hls"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
!res.dub?.sources.isNullOrEmpty() && !res.sub?.sources.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AniLoadResponse(
|
||||||
|
@JsonProperty("sub") val sub: DubSubSource?,
|
||||||
|
@JsonProperty("dub") val dub: DubSubSource?,
|
||||||
|
@JsonProperty("episodes") val episodes: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Sources(
|
||||||
|
@JsonProperty("file") val file: String?,
|
||||||
|
@JsonProperty("label") val label: String?,
|
||||||
|
@JsonProperty("type") val type: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DubSubSource(
|
||||||
|
@JsonProperty("Referer") var Referer: String?,
|
||||||
|
@JsonProperty("sources") var sources: ArrayList<Sources> = arrayListOf()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PageProps(
|
||||||
|
@JsonProperty("searchResults") val searchResults: SearchResults?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SearchResults(
|
||||||
|
@JsonProperty("Page") val Page: Page?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Page(
|
||||||
|
@JsonProperty("media") val media: ArrayList<Anime> = arrayListOf()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CoverImage(
|
||||||
|
@JsonProperty("color") val color: String?,
|
||||||
|
@JsonProperty("medium") val medium: String?,
|
||||||
|
@JsonProperty("large") val large: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Title(
|
||||||
|
@JsonProperty("english") val english: String?,
|
||||||
|
@JsonProperty("romaji") val romaji: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Search(
|
||||||
|
@JsonProperty("pageProps") val pageProps: PageProps?,
|
||||||
|
@JsonProperty("__N_SSP") val _NSSP: Boolean?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Anime(
|
||||||
|
@JsonProperty("status") val status: String?,
|
||||||
|
@JsonProperty("id") val id: Int?,
|
||||||
|
@JsonProperty("title") val title: Title?,
|
||||||
|
@JsonProperty("coverImage") val coverImage: CoverImage?,
|
||||||
|
@JsonProperty("format") val format: String?,
|
||||||
|
@JsonProperty("duration") val duration: Int?,
|
||||||
|
@JsonProperty("meanScore") val meanScore: Int?,
|
||||||
|
@JsonProperty("nextAiringEpisode") val nextAiringEpisode: String?,
|
||||||
|
@JsonProperty("bannerImage") val bannerImage: String?,
|
||||||
|
@JsonProperty("description") val description: String?,
|
||||||
|
@JsonProperty("genres") val genres: ArrayList<String>? = null,
|
||||||
|
@JsonProperty("season") val season: String?,
|
||||||
|
@JsonProperty("startDate") val startDate: StartDate?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class StartDate(
|
||||||
|
@JsonProperty("year") val year: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AnimeResponsePage(
|
||||||
|
@JsonProperty("pageProps") val pageProps: AnimeResponse?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AnimeResponse(
|
||||||
|
@JsonProperty("anime") val anime: Anime,
|
||||||
|
@JsonProperty("recommended") val recommended: ArrayList<Anime>,
|
||||||
|
@JsonProperty("episodes") val episodes: EpisodesParent,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class EpisodesParent(
|
||||||
|
@JsonProperty("id") val id: String?,
|
||||||
|
@JsonProperty("season") val season: String?,
|
||||||
|
@JsonProperty("startDate") val startDate: String?,
|
||||||
|
@JsonProperty("episodeCount") val episodeCount: Int?,
|
||||||
|
@JsonProperty("episodes") val episodes: Episodes?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Episodes(
|
||||||
|
@JsonProperty("nodes") val nodes: ArrayList<Nodes?> = arrayListOf()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Nodes(
|
||||||
|
@JsonProperty("number") val number: Int? = null,
|
||||||
|
@JsonProperty("titles") val titles: Titles?,
|
||||||
|
@JsonProperty("thumbnail") val thumbnail: Thumbnail?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Titles(
|
||||||
|
@JsonProperty("canonical") val canonical: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Original(
|
||||||
|
@JsonProperty("url") val url: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Thumbnail(
|
||||||
|
@JsonProperty("original") val original: Original?,
|
||||||
|
)
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import kotlinx.android.synthetic.main.fragment_downloads.*
|
import kotlinx.android.synthetic.main.fragment_downloads.*
|
||||||
|
|
||||||
|
|
||||||
const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
|
const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
|
||||||
|
|
||||||
class DownloadFragment : Fragment() {
|
class DownloadFragment : Fragment() {
|
||||||
|
@ -165,8 +166,22 @@ class DownloadFragment : Fragment() {
|
||||||
|
|
||||||
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
||||||
|
|
||||||
download_list.adapter = adapter
|
download_list?.adapter = adapter
|
||||||
download_list.layoutManager = GridLayoutManager(context, 1)
|
download_list?.layoutManager = GridLayoutManager(context, 1)
|
||||||
|
/*download_stream_button?.isGone = context?.isTvSettings() == true
|
||||||
|
download_stream_button?.setOnClickListener {
|
||||||
|
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
download_list?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
|
val dy = scrollY - oldScrollY
|
||||||
|
if (dy > 0) { //check for scroll down
|
||||||
|
download_stream_button?.shrink() // hide
|
||||||
|
} else if (dy < -5) {
|
||||||
|
download_stream_button?.extend() // show
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
downloadsViewModel.updateList(requireContext())
|
downloadsViewModel.updateList(requireContext())
|
||||||
|
|
||||||
context?.fixPaddingStatusbar(download_root)
|
context?.fixPaddingStatusbar(download_root)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
||||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
|
@ -94,10 +95,10 @@ class EpisodeAdapter(
|
||||||
@LayoutRes
|
@LayoutRes
|
||||||
private var layout: Int = 0
|
private var layout: Int = 0
|
||||||
fun updateLayout() {
|
fun updateLayout() {
|
||||||
// layout =
|
// layout =
|
||||||
// if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout
|
// if (cardList.filter { it.poster != null }.size >= cardList.size / 2f) // If over half has posters then use the large layout
|
||||||
// R.layout.result_episode_large
|
// R.layout.result_episode_large
|
||||||
// else R.layout.result_episode
|
// else R.layout.result_episode
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
@ -106,7 +107,8 @@ class EpisodeAdapter(
|
||||||
else R.layout.result_episode*/
|
else R.layout.result_episode*/
|
||||||
|
|
||||||
return EpisodeCardViewHolder(
|
return EpisodeCardViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(R.layout.result_episode_both, parent, false),
|
LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.result_episode_both, parent, false),
|
||||||
hasDownloadSupport,
|
hasDownloadSupport,
|
||||||
clickCallback,
|
clickCallback,
|
||||||
downloadClickCallback
|
downloadClickCallback
|
||||||
|
@ -144,7 +146,7 @@ class EpisodeAdapter(
|
||||||
fun bind(card: ResultEpisode) {
|
fun bind(card: ResultEpisode) {
|
||||||
localCard = card
|
localCard = card
|
||||||
|
|
||||||
val (parentView,otherView) = if(card.poster == null) {
|
val (parentView, otherView) = if (card.poster == null) {
|
||||||
itemView.episode_holder to itemView.episode_holder_large
|
itemView.episode_holder to itemView.episode_holder_large
|
||||||
} else {
|
} else {
|
||||||
itemView.episode_holder_large to itemView.episode_holder
|
itemView.episode_holder_large to itemView.episode_holder
|
||||||
|
@ -193,7 +195,7 @@ class EpisodeAdapter(
|
||||||
episodeRating?.isGone = episodeRating?.text.isNullOrBlank()
|
episodeRating?.isGone = episodeRating?.text.isNullOrBlank()
|
||||||
|
|
||||||
episodeDescript?.apply {
|
episodeDescript?.apply {
|
||||||
text = card.description ?: ""
|
text = card.description.html()
|
||||||
isGone = text.isNullOrBlank()
|
isGone = text.isNullOrBlank()
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
|
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.widget.*
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.NestedScrollView
|
import androidx.core.widget.NestedScrollView
|
||||||
|
@ -64,6 +65,7 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueT
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getDownloadSubsLanguageISO639_1
|
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getDownloadSubsLanguageISO639_1
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
|
import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
|
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
|
||||||
|
@ -103,9 +105,6 @@ import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
const val MAX_SYNO_LENGH = 1000
|
|
||||||
|
|
||||||
const val START_ACTION_NORMAL = 0
|
const val START_ACTION_NORMAL = 0
|
||||||
const val START_ACTION_RESUME_LATEST = 1
|
const val START_ACTION_RESUME_LATEST = 1
|
||||||
const val START_ACTION_LOAD_EP = 2
|
const val START_ACTION_LOAD_EP = 2
|
||||||
|
@ -1908,21 +1907,18 @@ class ResultFragment : ResultTrailerPlayer() {
|
||||||
)*/
|
)*/
|
||||||
//result_plot_header?.text =
|
//result_plot_header?.text =
|
||||||
// if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot)
|
// if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot)
|
||||||
if (!d.plot.isNullOrEmpty()) {
|
val syno = d.plot
|
||||||
var syno = d.plot!!
|
if (!syno.isNullOrEmpty()) {
|
||||||
if (syno.length > MAX_SYNO_LENGH) {
|
result_description?.setOnClickListener {
|
||||||
syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
|
|
||||||
}
|
|
||||||
result_description.setOnClickListener {
|
|
||||||
val builder: AlertDialog.Builder =
|
val builder: AlertDialog.Builder =
|
||||||
AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
|
AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
|
||||||
builder.setMessage(d.plot)
|
builder.setMessage(syno.html())
|
||||||
.setTitle(if (d.type == TvType.Torrent) R.string.torrent_plot else R.string.result_plot)
|
.setTitle(if (d.type == TvType.Torrent) R.string.torrent_plot else R.string.result_plot)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
result_description.text = syno
|
result_description?.text = syno.html()
|
||||||
} else {
|
} else {
|
||||||
result_description.text =
|
result_description?.text =
|
||||||
if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
|
if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
|
||||||
R.string.normal_no_plot
|
R.string.normal_no_plot
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,11 +18,14 @@ import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
import android.text.Spanned
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
import androidx.core.text.toSpanned
|
||||||
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
|
||||||
|
@ -61,6 +64,22 @@ object AppUtils {
|
||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
fun String?.html(): Spanned {
|
||||||
|
return getHtmlText(this ?: return "".toSpanned())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHtmlText(text: String): Spanned {
|
||||||
|
return try {
|
||||||
|
// I have no idea if this can throw any error, but I dont want to try
|
||||||
|
HtmlCompat.fromHtml(
|
||||||
|
text, HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
text.toSpanned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
private fun buildWatchNextProgramUri(
|
private fun buildWatchNextProgramUri(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -222,7 +241,7 @@ object AppUtils {
|
||||||
|
|
||||||
/** Any object as json string */
|
/** Any object as json string */
|
||||||
fun Any.toJson(): String {
|
fun Any.toJson(): String {
|
||||||
if(this is String) return this
|
if (this is String) return this
|
||||||
return mapper.writeValueAsString(this)
|
return mapper.writeValueAsString(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.utils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import com.lagradost.cloudstream3.animeproviders.AniflixProvider
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.mapper
|
import com.lagradost.cloudstream3.mapper
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
@ -81,7 +82,11 @@ object SyncUtil {
|
||||||
"https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json"
|
"https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json"
|
||||||
val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed<SyncPage>()
|
val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed<SyncPage>()
|
||||||
val pages = response.pages ?: return emptyList()
|
val pages = response.pages ?: return emptyList()
|
||||||
return pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values).mapNotNull { it.url }
|
val current = pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values).mapNotNull { it.url }.toMutableList()
|
||||||
|
if(type == "anilist") { // TODO MAKE BETTER
|
||||||
|
current.add("${AniflixProvider().mainUrl}/anime/$id")
|
||||||
|
}
|
||||||
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SyncPage(
|
data class SyncPage(
|
||||||
|
|
|
@ -35,9 +35,10 @@ class FlowLayout : ViewGroup {
|
||||||
measureChild(child, widthMeasureSpec, heightMeasureSpec)
|
measureChild(child, widthMeasureSpec, heightMeasureSpec)
|
||||||
val childWidth = child.measuredWidth
|
val childWidth = child.measuredWidth
|
||||||
val childHeight = child.measuredHeight
|
val childHeight = child.measuredHeight
|
||||||
|
currentHeight = max(currentHeight, currentChildHookPointy + childHeight)
|
||||||
|
|
||||||
//check if child can be placed in the current row, else go to next line
|
//check if child can be placed in the current row, else go to next line
|
||||||
if (currentChildHookPointx + childWidth - child.marginEnd - child.paddingEnd - itemSpacing > realWidth) {
|
if (currentChildHookPointx + childWidth - child.marginEnd - child.paddingEnd > realWidth) {
|
||||||
//new line
|
//new line
|
||||||
currentWidth = max(currentWidth, currentChildHookPointx)
|
currentWidth = max(currentWidth, currentChildHookPointx)
|
||||||
|
|
||||||
|
@ -47,8 +48,8 @@ class FlowLayout : ViewGroup {
|
||||||
}
|
}
|
||||||
val nextChildHookPointx =
|
val nextChildHookPointx =
|
||||||
currentChildHookPointx + childWidth + if (childWidth == 0) 0 else itemSpacing
|
currentChildHookPointx + childWidth + if (childWidth == 0) 0 else itemSpacing
|
||||||
|
|
||||||
val nextChildHookPointy = currentChildHookPointy
|
val nextChildHookPointy = currentChildHookPointy
|
||||||
currentHeight = max(currentHeight, currentChildHookPointy + childHeight)
|
|
||||||
val lp = child.layoutParams as LayoutParams
|
val lp = child.layoutParams as LayoutParams
|
||||||
lp.x = currentChildHookPointx
|
lp.x = currentChildHookPointx
|
||||||
lp.y = currentChildHookPointy
|
lp.y = currentChildHookPointy
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
app:cardBackgroundColor="?attr/boxItemBackground"
|
app:cardBackgroundColor="?attr/boxItemBackground"
|
||||||
android:id="@+id/episode_holder"
|
android:id="@+id/episode_holder"
|
||||||
android:foreground="@drawable/outline_drawable"
|
android:foreground="@drawable/outline_drawable"
|
||||||
android:layout_marginBottom="10dp">
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:background="?attr/primaryGrayBackground"
|
android:background="?attr/primaryGrayBackground"
|
||||||
|
tools:layout_height="100dp"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
<!--
|
<!--
|
||||||
|
@ -124,7 +125,6 @@
|
||||||
|
|
||||||
android:background="?attr/primaryBlackBackground"
|
android:background="?attr/primaryBlackBackground"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
android:padding="10dp"
|
|
||||||
android:id="@+id/download_list"
|
android:id="@+id/download_list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
tools:listitem="@layout/download_header_episode"
|
tools:listitem="@layout/download_header_episode"
|
||||||
|
@ -154,6 +154,7 @@
|
||||||
</ProgressBar>-->
|
</ProgressBar>-->
|
||||||
|
|
||||||
<com.facebook.shimmer.ShimmerFrameLayout
|
<com.facebook.shimmer.ShimmerFrameLayout
|
||||||
|
tools:visibility="gone"
|
||||||
android:id="@+id/download_loading"
|
android:id="@+id/download_loading"
|
||||||
app:shimmer_base_alpha="0.2"
|
app:shimmer_base_alpha="0.2"
|
||||||
app:shimmer_highlight_alpha="0.3"
|
app:shimmer_highlight_alpha="0.3"
|
||||||
|
@ -185,4 +186,13 @@
|
||||||
<include layout="@layout/loading_downloads" />
|
<include layout="@layout/loading_downloads" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
android:text="@string/stream"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:id="@+id/download_stream_button"
|
||||||
|
app:icon="@drawable/netflix_play"
|
||||||
|
style="@style/ExtendedFloatingActionButton"
|
||||||
|
android:textColor="?attr/textColor"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -363,6 +363,8 @@
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:maxLength="1000"
|
||||||
|
android:ellipsize="end"
|
||||||
android:id="@+id/result_description"
|
android:id="@+id/result_description"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -832,6 +834,7 @@
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
|
||||||
|
@ -842,6 +845,7 @@
|
||||||
android:textSize="17sp"
|
android:textSize="17sp"
|
||||||
android:textStyle="normal"
|
android:textStyle="normal"
|
||||||
android:text="Episode 1022 will be released in" />
|
android:text="Episode 1022 will be released in" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:paddingStart="5dp"
|
android:paddingStart="5dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
|
41
app/src/main/res/layout/stream_input.xml
Normal file
41
app/src/main/res/layout/stream_input.xml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:layout_weight="20"
|
||||||
|
android:id="@+id/subtitle_offset_input"
|
||||||
|
android:inputType="textUri"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/subtitle_offset_hint"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:gravity="bottom|end"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="60dp">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
style="@style/WhiteButton"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:visibility="visible"
|
||||||
|
android:text="@string/sort_apply"
|
||||||
|
android:id="@+id/apply_btt"
|
||||||
|
android:layout_width="wrap_content">
|
||||||
|
|
||||||
|
<requestFocus />
|
||||||
|
</com.google.android.material.button.MaterialButton>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
style="@style/BlackButton"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:text="@string/sort_cancel"
|
||||||
|
android:id="@+id/cancel_btt"
|
||||||
|
android:layout_width="wrap_content" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -136,6 +136,7 @@
|
||||||
<string name="download_canceled">Download Canceled</string>
|
<string name="download_canceled">Download Canceled</string>
|
||||||
<string name="download_done">Download Done</string>
|
<string name="download_done">Download Done</string>
|
||||||
<string name="download_format" translatable="false">%s - %s</string>
|
<string name="download_format" translatable="false">%s - %s</string>
|
||||||
|
<string name="stream">Stream</string>
|
||||||
|
|
||||||
<string name="error_loading_links_toast">Error Loading Links</string>
|
<string name="error_loading_links_toast">Error Loading Links</string>
|
||||||
<string name="download_storage_text">Internal Storage</string>
|
<string name="download_storage_text">Internal Storage</string>
|
||||||
|
|
Loading…
Reference in a new issue