episode options sff

This commit is contained in:
LagradOst 2021-07-17 16:14:25 +02:00
parent bcc17171e5
commit b5f913cc72
7 changed files with 285 additions and 134 deletions

View file

@ -27,6 +27,7 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.lagradost.cloudstream3.ui.result.ResultFragment
@ -151,6 +152,17 @@ object UIHelper {
return isCastApiAvailable
}
fun Context.isConnectedToChromecast(): Boolean {
if (isCastApiAvailable()) {
val castContext = CastContext.getSharedInstance(this)
if (castContext.castState == CastState.CONNECTED) {
return true
}
}
return false
}
fun adjustAlpha(@ColorInt color: Int, factor: Float): Int {
val alpha = (Color.alpha(color) * factor).roundToInt()
val red = Color.red(color)

View file

@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -9,6 +11,7 @@ import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.ContentLoadingProgressBar
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
@ -16,15 +19,27 @@ import com.bumptech.glide.load.model.GlideUrl
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable
import com.lagradost.cloudstream3.utils.getId
import kotlinx.android.synthetic.main.result_episode.view.episode_holder
import kotlinx.android.synthetic.main.result_episode.view.episode_text
import kotlinx.android.synthetic.main.result_episode_large.view.*
const val ACTION_RELOAD_EPISODE = 4
const val ACTION_CHROME_CAST_EPISODE = 2
const val ACTION_DOWNLOAD_EPISODE = 3
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
const val ACTION_PLAY_EPISODE_IN_EXTERNAL_PLAYER = 2
const val ACTION_PLAY_EPISODE_IN_BROWSER = 3
const val ACTION_CHROME_CAST_EPISODE = 4
const val ACTION_CHROME_CAST_MIRROR = 5
const val ACTION_DOWNLOAD_EPISODE = 6
const val ACTION_DOWNLOAD_MIRROR = 7
const val ACTION_RELOAD_EPISODE = 8
const val ACTION_COPY_LINK = 9
const val ACTION_SHOW_OPTIONS = 10
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
@ -69,8 +84,6 @@ class EpisodeAdapter(
itemView: View,
private val clickCallback: (EpisodeClickEvent) -> Unit,
) : RecyclerView.ViewHolder(itemView) {
//private val episodeViewPrecentage: View? = itemView.episode_view_procentage
// private val episodeViewPercentageOff: View? = itemView.episode_view_procentage_off
private val episodeText: TextView = itemView.episode_text
private val episodeRating: TextView? = itemView.episode_rating
private val episodeDescript: TextView? = itemView.episode_descript
@ -78,8 +91,6 @@ class EpisodeAdapter(
private val episodePoster: ImageView? = itemView.episode_poster
private val episodeDownload: ImageView? = itemView.episode_download
// val episodeExtra: ImageView = itemView.episode_extra
// private val episodePlay: ImageView = itemView.episode_play
private val episodeHolder = itemView.episode_holder
@SuppressLint("SetTextI18n")
@ -87,20 +98,7 @@ class EpisodeAdapter(
val name = if (card.name == null) "Episode ${card.episode}" else "${card.episode}. ${card.name}"
episodeText.text = name
fun setWidth(v: View, procentage: Float) {
val param = LinearLayout.LayoutParams(
v.layoutParams.width,
v.layoutParams.height,
procentage
)
v.layoutParams = param
}
val watchProgress = card.getWatchProgress()
/*if (episodeViewPrecentage != null && episodeViewPercentageOff != null) {
setWidth(episodeViewPrecentage, watchProgress)
setWidth(episodeViewPercentageOff, 1 - watchProgress)
}*/
episodeProgress?.progress = (watchProgress * 50).toInt()
episodeProgress?.visibility = if (watchProgress > 0.0f) View.VISIBLE else View.GONE
@ -133,21 +131,18 @@ class EpisodeAdapter(
episodeHolder.setOnClickListener {
episodeHolder.context?.let { ctx ->
if (ctx.isCastApiAvailable()) {
val castContext = CastContext.getSharedInstance(ctx)
if (castContext.castState == CastState.CONNECTED) {
clickCallback.invoke(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card))
} else {
// clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
}
if (ctx.isConnectedToChromecast()) {
clickCallback.invoke(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card))
} else {
// clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card)) //TODO REDO TO MAIN
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
}
}
}
episodeHolder.setOnLongClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
return@setOnLongClickListener true
}
episodeDownload?.setOnClickListener {

View file

@ -35,6 +35,7 @@ import com.lagradost.cloudstream3.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.UIHelper.getStatusBarHeight
import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable
import com.lagradost.cloudstream3.UIHelper.isConnectedToChromecast
import com.lagradost.cloudstream3.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIconsAndNoStringres
@ -43,14 +44,16 @@ import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.player.PlayerData
import com.lagradost.cloudstream3.ui.player.PlayerFragment
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.CastHelper.startCast
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.VideoDownloadManager
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.coroutines.Job
const val MAX_SYNO_LENGH = 300
@ -134,6 +137,7 @@ class ResultFragment : Fragment() {
private var currentLoadingCount = 0 // THIS IS USED TO PREVENT LATE EVENTS, AFTER DISMISS WAS CLICKED
private lateinit var viewModel: ResultViewModel
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
private var allEpisodesSubs: HashMap<Int, ArrayList<SubtitleFile>> = HashMap()
private var currentHeaderName: String? = null
private var currentType: TvType? = null
private var currentEpisodes: List<ResultEpisode>? = null
@ -188,6 +192,7 @@ class ResultFragment : Fragment() {
}
private var currentPoster: String? = null
private var currentId: Int? = null
private var currentIsMovie: Boolean? = null
var url: String? = null
@ -255,59 +260,126 @@ class ResultFragment : Fragment() {
requireActivity().popCurrentPage()
}
fun handleAction(episodeClick: EpisodeClickEvent) {
fun handleAction(episodeClick: EpisodeClickEvent): Job = main {
//val id = episodeClick.data.id
val index = episodeClick.data.index
val buildInPlayer = true
currentLoadingCount++
var currentLinks: ArrayList<ExtractorLink>? = null
var currentSubs: ArrayList<SubtitleFile>? = null
suspend fun requireLinks(isCasting: Boolean = false): Boolean {
val currentLinksTemp =
if (allEpisodes.containsKey(episodeClick.data.id)) allEpisodes[episodeClick.data.id] else null
val currentSubsTemp =
if (allEpisodes.containsKey(episodeClick.data.id)) allEpisodes[episodeClick.data.id] else null
if (currentLinksTemp != null && currentLinksTemp.size > 0) {
currentLinks = currentLinksTemp
return true
}
val skipLoading = if (apiName != null) {
getApiFromName(apiName).instantLinkLoading
} else false
var loadingDialog: AlertDialog? = null
val currentLoad = currentLoadingCount
if (!skipLoading) {
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustomTransparent)
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null)
builder.setView(customLayout)
loadingDialog = builder.create()
loadingDialog.show()
loadingDialog.setOnDismissListener {
currentLoadingCount++
}
}
val data = viewModel.loadEpisode(episodeClick.data, isCasting)
if (currentLoadingCount != currentLoad) return false
loadingDialog?.dismiss()
when (data) {
is Resource.Success -> {
currentLinks = data.value.links
currentSubs = data.value.subs
return true
}
is Resource.Failure -> {
Toast.makeText(requireContext(), R.string.error_loading_links, Toast.LENGTH_SHORT).show()
}
else -> {
}
}
return false
}
val isLoaded = when (episodeClick.action) {
ACTION_PLAY_EPISODE_IN_PLAYER -> true
ACTION_CHROME_CAST_EPISODE -> requireLinks(true)
ACTION_CHROME_CAST_MIRROR -> requireLinks(true)
else -> requireLinks()
}
if (!isLoaded) return@main // CANT LOAD
when (episodeClick.action) {
ACTION_CHROME_CAST_EPISODE -> {
val skipLoading = if (apiName != null) {
getApiFromName(apiName).instantLinkLoading
} else false
ACTION_SHOW_OPTIONS -> {
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
var dialog: AlertDialog? = null
val currentLoad = currentLoadingCount
builder.setTitle(episodeClick.data.name)
val options = requireContext().resources.getStringArray(R.array.episode_long_click_options)
val optionsValues =
requireContext().resources.getIntArray(R.array.episode_long_click_options_values)
if (!skipLoading) {
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustomTransparent)
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null)
builder.setView(customLayout)
val verifiedOptions = ArrayList<String>()
val verifiedOptionsValues = ArrayList<Int>()
dialog = builder.create()
for (i in options.indices) {
val opv = optionsValues[i]
val op = options[i]
dialog.show()
dialog.setOnDismissListener {
currentLoadingCount++
val isConnected = requireContext().isConnectedToChromecast()
val add = when (opv) {
ACTION_CHROME_CAST_EPISODE -> isConnected
ACTION_CHROME_CAST_MIRROR -> isConnected
else -> true
}
if (add) {
verifiedOptions.add(op)
verifiedOptionsValues.add(opv)
}
}
// Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show()
viewModel.loadEpisode(episodeClick.data, true) { data ->
if (currentLoadingCount != currentLoad) return@loadEpisode
builder.setItems(
verifiedOptions.toTypedArray()
) { _, which ->
handleAction(EpisodeClickEvent(verifiedOptionsValues[which], episodeClick.data))
dialog?.dismiss()
when (data) {
is Resource.Failure -> {
Toast.makeText(activity, "Failed to load links", Toast.LENGTH_SHORT).show()
}
is Resource.Success -> {
val eps = currentEpisodes ?: return@loadEpisode
context?.startCast(
apiName ?: return@loadEpisode,
currentIsMovie ?: return@loadEpisode,
currentHeaderName,
currentPoster,
episodeClick.data.index,
eps,
sortUrls(data.value.links),
data.value.subs,
startTime = episodeClick.data.getRealPosition(),
)
}
}
}
dialog = builder.create()
dialog.show()
}
ACTION_CHROME_CAST_EPISODE -> {
val eps = currentEpisodes ?: return@main
context?.startCast(
apiName ?: return@main,
currentIsMovie ?: return@main,
currentHeaderName,
currentPoster,
episodeClick.data.index,
eps,
sortUrls(currentLinks ?: return@main),
currentSubs ?: return@main,
startTime = episodeClick.data.getRealPosition(),
)
}
ACTION_PLAY_EPISODE_IN_PLAYER -> {
@ -330,60 +402,80 @@ class ResultFragment : Fragment() {
}
}
ACTION_RELOAD_EPISODE -> {
/*viewModel.load(episodeClick.data) { res ->
if (res is Resource.Success) {
playEpisode(allEpisodes[id], index)
}
}*/
viewModel.loadEpisode(episodeClick.data, false)
}
ACTION_DOWNLOAD_EPISODE -> {
val tempUrl = url
if (tempUrl != null) {
viewModel.loadEpisode(episodeClick.data, true) { data ->
if (data is Resource.Success) {
val isMovie = currentIsMovie ?: return@loadEpisode
val titleName = sanitizeFilename(currentHeaderName ?: return@loadEpisode)
val meta = VideoDownloadManager.DownloadEpisodeMetadata(
episodeClick.data.id,
titleName,
apiName ?: return@loadEpisode,
episodeClick.data.poster ?: currentPoster,
episodeClick.data.name,
if (isMovie) null else episodeClick.data.season,
if (isMovie) null else episodeClick.data.episode
)
val isMovie = currentIsMovie ?: return@main
val titleName = sanitizeFilename(currentHeaderName ?: return@main)
val folder = when (currentType) {
TvType.Anime -> "Anime/$titleName"
TvType.Movie -> "Movies"
TvType.TvSeries -> "TVSeries/$titleName"
TvType.ONA -> "ONA"
else -> null
}
val meta = VideoDownloadManager.DownloadEpisodeMetadata(
episodeClick.data.id,
titleName,
apiName ?: return@main,
episodeClick.data.poster ?: currentPoster,
episodeClick.data.name,
if (isMovie) null else episodeClick.data.season,
if (isMovie) null else episodeClick.data.episode
)
VideoDownloadManager.downloadEpisode(
requireContext(),
tempUrl,
folder,
meta,
data.value.links
)
}
}
val folder = when (currentType) {
TvType.Anime -> "Anime/$titleName"
TvType.Movie -> "Movies"
TvType.TvSeries -> "TVSeries/$titleName"
TvType.ONA -> "ONA"
else -> null
}
context?.let { ctx ->
// SET VISUAL KEYS
ctx.setKey(
DOWNLOAD_HEADER_CACHE, (currentId ?: return@let).toString(),
VideoDownloadHelper.DownloadHeaderCached(
apiName,
url ?: return@let,
currentType ?: return@let,
currentHeaderName ?: return@let,
currentPoster ?: return@let,
currentId ?: return@let
)
)
val epData = episodeClick.data
ctx.setKey(
DOWNLOAD_EPISODE_CACHE,
epData.id.toString(),
VideoDownloadHelper.DownloadEpisodeCached(
epData.name,
epData.poster,
epData.episode,
epData.season,
epData.id,
currentId ?: return@let,
epData.rating,
epData.descript
)
)
// DOWNLOAD VIDEO
VideoDownloadManager.downloadEpisode(
ctx,
url ?: return@main,
folder,
meta,
currentLinks ?: return@main
)
}
}
}
}
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? = activity?.let { it ->
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
EpisodeAdapter(
it,
ArrayList(),
result_episodes,
) { episodeClick ->
handleAction(episodeClick)
}
}
result_episodes.adapter = adapter
result_episodes.layoutManager = GridLayoutManager(context, 1)
@ -409,6 +501,12 @@ class ResultFragment : Fragment() {
allEpisodes = it
}
observe(viewModel.allEpisodesSubs) {
allEpisodesSubs = it
}
observe(viewModel.selectedSeason) { season ->
result_season_button?.text = fromIndexToSeasonText(season)
}
@ -437,6 +535,10 @@ class ResultFragment : Fragment() {
(result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged()
}
observe(viewModel.id) {
currentId = it
}
observe(viewModel.resultResponse) { data ->
when (data) {
is Resource.Success -> {

View file

@ -26,7 +26,7 @@ class ResultViewModel : ViewModel() {
private val dubStatus: MutableLiveData<DubStatus> = MutableLiveData()
private val page: MutableLiveData<LoadResponse> = MutableLiveData()
private val id: MutableLiveData<Int> = MutableLiveData()
val id: MutableLiveData<Int> = MutableLiveData()
val selectedSeason: MutableLiveData<Int> = MutableLiveData(-2)
val seasonSelections: MutableLiveData<List<Int?>> = MutableLiveData()
@ -208,6 +208,41 @@ class ResultViewModel : ViewModel() {
loadEpisode(episode.id, episode.data, isCasting, callback)
}
suspend fun loadEpisode(
episode: ResultEpisode,
isCasting: Boolean,
) : Resource<ResultViewModel.EpisodeData> {
return loadEpisode(episode.id, episode.data, isCasting)
}
private suspend fun loadEpisode(
id: Int,
data: String,
isCasting: Boolean,
): Resource<ResultViewModel.EpisodeData> {
if (_allEpisodes.value?.contains(id) == true) {
_allEpisodes.value?.remove(id)
}
val links = ArrayList<ExtractorLink>()
val subs = ArrayList<SubtitleFile>()
return safeApiCall {
getApiFromName(_apiName.value).loadLinks(data, isCasting, { subtitleFile ->
if (!subs.any { it.url == subtitleFile.url }) {
subs.add(subtitleFile)
_allEpisodesSubs.value?.set(id, subs)
_allEpisodesSubs.postValue(_allEpisodesSubs.value)
}
}) { link ->
if (!links.any { it.url == link.url }) {
links.add(link)
_allEpisodes.value?.set(id, links)
_allEpisodes.postValue(_allEpisodes.value)
}
}
EpisodeData(links, subs)
}
}
private fun loadEpisode(
id: Int,
data: String,
@ -215,27 +250,7 @@ class ResultViewModel : ViewModel() {
callback: (Resource<EpisodeData>) -> Unit,
) =
viewModelScope.launch {
if (_allEpisodes.value?.contains(id) == true) {
_allEpisodes.value?.remove(id)
}
val links = ArrayList<ExtractorLink>()
val subs = ArrayList<SubtitleFile>()
val localData = safeApiCall {
getApiFromName(_apiName.value).loadLinks(data, isCasting, { subtitleFile ->
if (!subs.any { it.url == subtitleFile.url }) {
subs.add(subtitleFile)
_allEpisodesSubs.value?.set(id, subs)
_allEpisodesSubs.postValue(_allEpisodesSubs.value)
}
}) { link ->
if (!links.any { it.url == link.url }) {
links.add(link)
_allEpisodes.value?.set(id, links)
_allEpisodes.postValue(_allEpisodes.value)
}
}
EpisodeData(links, subs)
}
val localData = loadEpisode(id, data, isCasting)
callback.invoke(localData)
}

View file

@ -6,6 +6,9 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache"
const val PREFERENCES_NAME: String = "rebuild_preference"
object DataStore {

View file

@ -13,4 +13,27 @@
<!-- Actually fake to make the skip op button the same style -->
<item>@id/cast_button_type_forward_30_seconds</item>
</array>
<array name="episode_long_click_options">
<item>Chromecast Episode</item>
<item>Chromecast Mirror</item>
<item>Play In App</item>
<item>Play In External App</item>
<item>Play In Browser</item>
<item>Copy Link</item>
<item>Auto Download</item>
<item>Download Mirror</item>
<item>Reload Links</item>
</array>
<array name="episode_long_click_options_values">
<item>4</item>
<item>5</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>9</item>
<item>6</item>
<item>7</item>
<item>8</item>
</array>
</resources>

View file

@ -39,4 +39,5 @@
<string name="play_episode">Play Episode</string>
<string name="need_storage">Allow to download episodes</string>
<string name="download_descript">Download</string>
<string name="error_loading_links">Error Loading Links</string>
</resources>