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(),
 | 
			
		||||
            NginxProvider(),
 | 
			
		||||
            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 kotlinx.android.synthetic.main.fragment_downloads.*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
 | 
			
		||||
 | 
			
		||||
class DownloadFragment : Fragment() {
 | 
			
		||||
| 
						 | 
				
			
			@ -165,8 +166,22 @@ class DownloadFragment : Fragment() {
 | 
			
		|||
 | 
			
		||||
        downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
 | 
			
		||||
 | 
			
		||||
        download_list.adapter = adapter
 | 
			
		||||
        download_list.layoutManager = GridLayoutManager(context, 1)
 | 
			
		||||
        download_list?.adapter = adapter
 | 
			
		||||
        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())
 | 
			
		||||
 | 
			
		||||
        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.EasyDownloadButton
 | 
			
		||||
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.VideoDownloadHelper
 | 
			
		||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
 | 
			
		||||
| 
						 | 
				
			
			@ -94,10 +95,10 @@ class EpisodeAdapter(
 | 
			
		|||
    @LayoutRes
 | 
			
		||||
    private var layout: Int = 0
 | 
			
		||||
    fun updateLayout() {
 | 
			
		||||
       // 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
 | 
			
		||||
      //      else R.layout.result_episode
 | 
			
		||||
        // 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
 | 
			
		||||
        //      else R.layout.result_episode
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +107,8 @@ class EpisodeAdapter(
 | 
			
		|||
        else R.layout.result_episode*/
 | 
			
		||||
 | 
			
		||||
        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,
 | 
			
		||||
            clickCallback,
 | 
			
		||||
            downloadClickCallback
 | 
			
		||||
| 
						 | 
				
			
			@ -144,7 +146,7 @@ class EpisodeAdapter(
 | 
			
		|||
        fun bind(card: ResultEpisode) {
 | 
			
		||||
            localCard = card
 | 
			
		||||
 | 
			
		||||
            val (parentView,otherView) = if(card.poster == null) {
 | 
			
		||||
            val (parentView, otherView) = if (card.poster == null) {
 | 
			
		||||
                itemView.episode_holder to itemView.episode_holder_large
 | 
			
		||||
            } else {
 | 
			
		||||
                itemView.episode_holder_large to itemView.episode_holder
 | 
			
		||||
| 
						 | 
				
			
			@ -193,7 +195,7 @@ class EpisodeAdapter(
 | 
			
		|||
            episodeRating?.isGone = episodeRating?.text.isNullOrBlank()
 | 
			
		||||
 | 
			
		||||
            episodeDescript?.apply {
 | 
			
		||||
                text = card.description ?: ""
 | 
			
		||||
                text = card.description.html()
 | 
			
		||||
                isGone = text.isNullOrBlank()
 | 
			
		||||
                setOnClickListener {
 | 
			
		||||
                    clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ import android.widget.*
 | 
			
		|||
import androidx.annotation.StringRes
 | 
			
		||||
import androidx.appcompat.app.AlertDialog
 | 
			
		||||
import androidx.core.content.FileProvider
 | 
			
		||||
import androidx.core.text.HtmlCompat
 | 
			
		||||
import androidx.core.view.isGone
 | 
			
		||||
import androidx.core.view.isVisible
 | 
			
		||||
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.subtitles.SubtitlesFragment.Companion.getDownloadSubsLanguageISO639_1
 | 
			
		||||
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.isCastApiAvailable
 | 
			
		||||
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
 | 
			
		||||
| 
						 | 
				
			
			@ -103,9 +105,6 @@ import kotlinx.coroutines.withContext
 | 
			
		|||
import java.io.File
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const val MAX_SYNO_LENGH = 1000
 | 
			
		||||
 | 
			
		||||
const val START_ACTION_NORMAL = 0
 | 
			
		||||
const val START_ACTION_RESUME_LATEST = 1
 | 
			
		||||
const val START_ACTION_LOAD_EP = 2
 | 
			
		||||
| 
						 | 
				
			
			@ -1908,21 +1907,18 @@ class ResultFragment : ResultTrailerPlayer() {
 | 
			
		|||
                        )*/
 | 
			
		||||
                    //result_plot_header?.text =
 | 
			
		||||
                    //    if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot)
 | 
			
		||||
                    if (!d.plot.isNullOrEmpty()) {
 | 
			
		||||
                        var syno = d.plot!!
 | 
			
		||||
                        if (syno.length > MAX_SYNO_LENGH) {
 | 
			
		||||
                            syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
 | 
			
		||||
                        }
 | 
			
		||||
                        result_description.setOnClickListener {
 | 
			
		||||
                    val syno = d.plot
 | 
			
		||||
                    if (!syno.isNullOrEmpty()) {
 | 
			
		||||
                        result_description?.setOnClickListener {
 | 
			
		||||
                            val builder: AlertDialog.Builder =
 | 
			
		||||
                                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)
 | 
			
		||||
                                .show()
 | 
			
		||||
                        }
 | 
			
		||||
                        result_description.text = syno
 | 
			
		||||
                        result_description?.text = syno.html()
 | 
			
		||||
                    } else {
 | 
			
		||||
                        result_description.text =
 | 
			
		||||
                        result_description?.text =
 | 
			
		||||
                            if (d.type == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
 | 
			
		||||
                                R.string.normal_no_plot
 | 
			
		||||
                            )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,11 +18,14 @@ import android.os.Build
 | 
			
		|||
import android.os.Environment
 | 
			
		||||
import android.os.ParcelFileDescriptor
 | 
			
		||||
import android.provider.MediaStore
 | 
			
		||||
import android.text.Spanned
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import androidx.annotation.RequiresApi
 | 
			
		||||
import androidx.annotation.WorkerThread
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
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.TvContractCompat
 | 
			
		||||
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")
 | 
			
		||||
    private fun buildWatchNextProgramUri(
 | 
			
		||||
        context: Context,
 | 
			
		||||
| 
						 | 
				
			
			@ -222,7 +241,7 @@ object AppUtils {
 | 
			
		|||
 | 
			
		||||
    /** Any object as json string */
 | 
			
		||||
    fun Any.toJson(): String {
 | 
			
		||||
        if(this is String) return this
 | 
			
		||||
        if (this is String) return this
 | 
			
		||||
        return mapper.writeValueAsString(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.utils
 | 
			
		|||
import android.util.Log
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty
 | 
			
		||||
import com.fasterxml.jackson.module.kotlin.readValue
 | 
			
		||||
import com.lagradost.cloudstream3.animeproviders.AniflixProvider
 | 
			
		||||
import com.lagradost.cloudstream3.app
 | 
			
		||||
import com.lagradost.cloudstream3.mapper
 | 
			
		||||
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"
 | 
			
		||||
        val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed<SyncPage>()
 | 
			
		||||
        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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,9 +35,10 @@ class FlowLayout : ViewGroup {
 | 
			
		|||
            measureChild(child, widthMeasureSpec, heightMeasureSpec)
 | 
			
		||||
            val childWidth = child.measuredWidth
 | 
			
		||||
            val childHeight = child.measuredHeight
 | 
			
		||||
            currentHeight = max(currentHeight, currentChildHookPointy + childHeight)
 | 
			
		||||
 | 
			
		||||
            //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
 | 
			
		||||
                currentWidth = max(currentWidth, currentChildHookPointx)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,8 +48,8 @@ class FlowLayout : ViewGroup {
 | 
			
		|||
            }
 | 
			
		||||
            val nextChildHookPointx =
 | 
			
		||||
                currentChildHookPointx + childWidth + if (childWidth == 0) 0 else itemSpacing
 | 
			
		||||
 | 
			
		||||
            val nextChildHookPointy = currentChildHookPointy
 | 
			
		||||
            currentHeight = max(currentHeight, currentChildHookPointy + childHeight)
 | 
			
		||||
            val lp = child.layoutParams as LayoutParams
 | 
			
		||||
            lp.x = currentChildHookPointx
 | 
			
		||||
            lp.y = currentChildHookPointy
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,9 @@
 | 
			
		|||
        app:cardBackgroundColor="?attr/boxItemBackground"
 | 
			
		||||
        android:id="@+id/episode_holder"
 | 
			
		||||
        android:foreground="@drawable/outline_drawable"
 | 
			
		||||
        android:layout_marginBottom="10dp">
 | 
			
		||||
        android:layout_marginStart="10dp"
 | 
			
		||||
        android:layout_marginEnd="10dp"
 | 
			
		||||
        android:layout_marginTop="10dp">
 | 
			
		||||
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
            android:foreground="?android:attr/selectableItemBackgroundBorderless"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@
 | 
			
		|||
 | 
			
		||||
    <com.google.android.material.appbar.AppBarLayout
 | 
			
		||||
            android:background="?attr/primaryGrayBackground"
 | 
			
		||||
            tools:layout_height="100dp"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content">
 | 
			
		||||
        <!--
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +125,6 @@
 | 
			
		|||
 | 
			
		||||
            android:background="?attr/primaryBlackBackground"
 | 
			
		||||
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
 | 
			
		||||
            android:padding="10dp"
 | 
			
		||||
            android:id="@+id/download_list"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            tools:listitem="@layout/download_header_episode"
 | 
			
		||||
| 
						 | 
				
			
			@ -154,6 +154,7 @@
 | 
			
		|||
    </ProgressBar>-->
 | 
			
		||||
 | 
			
		||||
    <com.facebook.shimmer.ShimmerFrameLayout
 | 
			
		||||
            tools:visibility="gone"
 | 
			
		||||
            android:id="@+id/download_loading"
 | 
			
		||||
            app:shimmer_base_alpha="0.2"
 | 
			
		||||
            app:shimmer_highlight_alpha="0.3"
 | 
			
		||||
| 
						 | 
				
			
			@ -185,4 +186,13 @@
 | 
			
		|||
            <include layout="@layout/loading_downloads" />
 | 
			
		||||
        </LinearLayout>
 | 
			
		||||
    </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>
 | 
			
		||||
| 
						 | 
				
			
			@ -363,6 +363,8 @@
 | 
			
		|||
                                    android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
                                <TextView
 | 
			
		||||
                                        android:maxLength="1000"
 | 
			
		||||
                                        android:ellipsize="end"
 | 
			
		||||
                                        android:id="@+id/result_description"
 | 
			
		||||
                                        android:layout_width="match_parent"
 | 
			
		||||
                                        android:layout_height="wrap_content"
 | 
			
		||||
| 
						 | 
				
			
			@ -832,6 +834,7 @@
 | 
			
		|||
                                    android:orientation="horizontal"
 | 
			
		||||
                                    android:layout_width="wrap_content"
 | 
			
		||||
                                    android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
                                <TextView
 | 
			
		||||
                                        android:gravity="center"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -842,6 +845,7 @@
 | 
			
		|||
                                        android:textSize="17sp"
 | 
			
		||||
                                        android:textStyle="normal"
 | 
			
		||||
                                        android:text="Episode 1022 will be released in" />
 | 
			
		||||
 | 
			
		||||
                                <TextView
 | 
			
		||||
                                        android:paddingStart="5dp"
 | 
			
		||||
                                        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_done">Download Done</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="download_storage_text">Internal Storage</string>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue