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.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.gms.cast.framework.CastContext 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.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.GoogleApiAvailability
import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultFragment
@ -151,6 +152,17 @@ object UIHelper {
return isCastApiAvailable 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 { fun adjustAlpha(@ColorInt color: Int, factor: Float): Int {
val alpha = (Color.alpha(color) * factor).roundToInt() val alpha = (Color.alpha(color) * factor).roundToInt()
val red = Color.red(color) val red = Color.red(color)

View file

@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -9,6 +11,7 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.ContentLoadingProgressBar import androidx.core.widget.ContentLoadingProgressBar
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide 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.CastContext
import com.google.android.gms.cast.framework.CastState import com.google.android.gms.cast.framework.CastState
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable 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_holder
import kotlinx.android.synthetic.main.result_episode.view.episode_text import kotlinx.android.synthetic.main.result_episode.view.episode_text
import kotlinx.android.synthetic.main.result_episode_large.view.* 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_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) data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
@ -69,8 +84,6 @@ class EpisodeAdapter(
itemView: View, itemView: View,
private val clickCallback: (EpisodeClickEvent) -> Unit, private val clickCallback: (EpisodeClickEvent) -> Unit,
) : RecyclerView.ViewHolder(itemView) { ) : 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 episodeText: TextView = itemView.episode_text
private val episodeRating: TextView? = itemView.episode_rating private val episodeRating: TextView? = itemView.episode_rating
private val episodeDescript: TextView? = itemView.episode_descript private val episodeDescript: TextView? = itemView.episode_descript
@ -78,8 +91,6 @@ class EpisodeAdapter(
private val episodePoster: ImageView? = itemView.episode_poster private val episodePoster: ImageView? = itemView.episode_poster
private val episodeDownload: ImageView? = itemView.episode_download 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 private val episodeHolder = itemView.episode_holder
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@ -87,20 +98,7 @@ class EpisodeAdapter(
val name = if (card.name == null) "Episode ${card.episode}" else "${card.episode}. ${card.name}" val name = if (card.name == null) "Episode ${card.episode}" else "${card.episode}. ${card.name}"
episodeText.text = 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() val watchProgress = card.getWatchProgress()
/*if (episodeViewPrecentage != null && episodeViewPercentageOff != null) {
setWidth(episodeViewPrecentage, watchProgress)
setWidth(episodeViewPercentageOff, 1 - watchProgress)
}*/
episodeProgress?.progress = (watchProgress * 50).toInt() episodeProgress?.progress = (watchProgress * 50).toInt()
episodeProgress?.visibility = if (watchProgress > 0.0f) View.VISIBLE else View.GONE episodeProgress?.visibility = if (watchProgress > 0.0f) View.VISIBLE else View.GONE
@ -133,21 +131,18 @@ class EpisodeAdapter(
episodeHolder.setOnClickListener { episodeHolder.setOnClickListener {
episodeHolder.context?.let { ctx -> episodeHolder.context?.let { ctx ->
if (ctx.isCastApiAvailable()) { if (ctx.isConnectedToChromecast()) {
val castContext = CastContext.getSharedInstance(ctx) clickCallback.invoke(EpisodeClickEvent(ACTION_CHROME_CAST_EPISODE, card))
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))
}
} else { } else {
// clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card))
clickCallback.invoke(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, card)) //TODO REDO TO MAIN
} }
} }
}
episodeHolder.setOnLongClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
return@setOnLongClickListener true
} }
episodeDownload?.setOnClickListener { 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.fixPaddingStatusbar
import com.lagradost.cloudstream3.UIHelper.getStatusBarHeight import com.lagradost.cloudstream3.UIHelper.getStatusBarHeight
import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable
import com.lagradost.cloudstream3.UIHelper.isConnectedToChromecast
import com.lagradost.cloudstream3.UIHelper.popCurrentPage import com.lagradost.cloudstream3.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons
import com.lagradost.cloudstream3.UIHelper.popupMenuNoIconsAndNoStringres 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.WatchType
import com.lagradost.cloudstream3.ui.player.PlayerData import com.lagradost.cloudstream3.ui.player.PlayerData
import com.lagradost.cloudstream3.ui.player.PlayerFragment import com.lagradost.cloudstream3.ui.player.PlayerFragment
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.CastHelper.startCast 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.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.VideoDownloadManager
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.coroutines.Job
const val MAX_SYNO_LENGH = 300 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 var currentLoadingCount = 0 // THIS IS USED TO PREVENT LATE EVENTS, AFTER DISMISS WAS CLICKED
private lateinit var viewModel: ResultViewModel private lateinit var viewModel: ResultViewModel
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap() private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
private var allEpisodesSubs: HashMap<Int, ArrayList<SubtitleFile>> = HashMap()
private var currentHeaderName: String? = null private var currentHeaderName: String? = null
private var currentType: TvType? = null private var currentType: TvType? = null
private var currentEpisodes: List<ResultEpisode>? = null private var currentEpisodes: List<ResultEpisode>? = null
@ -188,6 +192,7 @@ class ResultFragment : Fragment() {
} }
private var currentPoster: String? = null private var currentPoster: String? = null
private var currentId: Int? = null
private var currentIsMovie: Boolean? = null private var currentIsMovie: Boolean? = null
var url: String? = null var url: String? = null
@ -255,59 +260,126 @@ class ResultFragment : Fragment() {
requireActivity().popCurrentPage() requireActivity().popCurrentPage()
} }
fun handleAction(episodeClick: EpisodeClickEvent) { fun handleAction(episodeClick: EpisodeClickEvent): Job = main {
//val id = episodeClick.data.id //val id = episodeClick.data.id
val index = episodeClick.data.index val index = episodeClick.data.index
val buildInPlayer = true val buildInPlayer = true
currentLoadingCount++ 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) { when (episodeClick.action) {
ACTION_CHROME_CAST_EPISODE -> { ACTION_SHOW_OPTIONS -> {
val skipLoading = if (apiName != null) { val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
getApiFromName(apiName).instantLinkLoading
} else false
var dialog: AlertDialog? = null 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 verifiedOptions = ArrayList<String>()
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustomTransparent) val verifiedOptionsValues = ArrayList<Int>()
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null)
builder.setView(customLayout)
dialog = builder.create() for (i in options.indices) {
val opv = optionsValues[i]
val op = options[i]
dialog.show() val isConnected = requireContext().isConnectedToChromecast()
dialog.setOnDismissListener { val add = when (opv) {
currentLoadingCount++ 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() builder.setItems(
verifiedOptions.toTypedArray()
viewModel.loadEpisode(episodeClick.data, true) { data -> ) { _, which ->
if (currentLoadingCount != currentLoad) return@loadEpisode handleAction(EpisodeClickEvent(verifiedOptionsValues[which], episodeClick.data))
dialog?.dismiss() 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 -> { ACTION_PLAY_EPISODE_IN_PLAYER -> {
@ -330,60 +402,80 @@ class ResultFragment : Fragment() {
} }
} }
ACTION_RELOAD_EPISODE -> { ACTION_RELOAD_EPISODE -> {
/*viewModel.load(episodeClick.data) { res -> viewModel.loadEpisode(episodeClick.data, false)
if (res is Resource.Success) {
playEpisode(allEpisodes[id], index)
}
}*/
} }
ACTION_DOWNLOAD_EPISODE -> { ACTION_DOWNLOAD_EPISODE -> {
val tempUrl = url val isMovie = currentIsMovie ?: return@main
if (tempUrl != null) { val titleName = sanitizeFilename(currentHeaderName ?: return@main)
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 folder = when (currentType) { val meta = VideoDownloadManager.DownloadEpisodeMetadata(
TvType.Anime -> "Anime/$titleName" episodeClick.data.id,
TvType.Movie -> "Movies" titleName,
TvType.TvSeries -> "TVSeries/$titleName" apiName ?: return@main,
TvType.ONA -> "ONA" episodeClick.data.poster ?: currentPoster,
else -> null episodeClick.data.name,
} if (isMovie) null else episodeClick.data.season,
if (isMovie) null else episodeClick.data.episode
)
VideoDownloadManager.downloadEpisode( val folder = when (currentType) {
requireContext(), TvType.Anime -> "Anime/$titleName"
tempUrl, TvType.Movie -> "Movies"
folder, TvType.TvSeries -> "TVSeries/$titleName"
meta, TvType.ONA -> "ONA"
data.value.links 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( EpisodeAdapter(
it,
ArrayList(), ArrayList(),
result_episodes,
) { episodeClick -> ) { episodeClick ->
handleAction(episodeClick) handleAction(episodeClick)
} }
}
result_episodes.adapter = adapter result_episodes.adapter = adapter
result_episodes.layoutManager = GridLayoutManager(context, 1) result_episodes.layoutManager = GridLayoutManager(context, 1)
@ -409,6 +501,12 @@ class ResultFragment : Fragment() {
allEpisodes = it allEpisodes = it
} }
observe(viewModel.allEpisodesSubs) {
allEpisodesSubs = it
}
observe(viewModel.selectedSeason) { season -> observe(viewModel.selectedSeason) { season ->
result_season_button?.text = fromIndexToSeasonText(season) result_season_button?.text = fromIndexToSeasonText(season)
} }
@ -437,6 +535,10 @@ class ResultFragment : Fragment() {
(result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged() (result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged()
} }
observe(viewModel.id) {
currentId = it
}
observe(viewModel.resultResponse) { data -> observe(viewModel.resultResponse) { data ->
when (data) { when (data) {
is Resource.Success -> { is Resource.Success -> {

View file

@ -26,7 +26,7 @@ class ResultViewModel : ViewModel() {
private val dubStatus: MutableLiveData<DubStatus> = MutableLiveData() private val dubStatus: MutableLiveData<DubStatus> = MutableLiveData()
private val page: MutableLiveData<LoadResponse> = 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 selectedSeason: MutableLiveData<Int> = MutableLiveData(-2)
val seasonSelections: MutableLiveData<List<Int?>> = MutableLiveData() val seasonSelections: MutableLiveData<List<Int?>> = MutableLiveData()
@ -208,6 +208,41 @@ class ResultViewModel : ViewModel() {
loadEpisode(episode.id, episode.data, isCasting, callback) 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( private fun loadEpisode(
id: Int, id: Int,
data: String, data: String,
@ -215,27 +250,7 @@ class ResultViewModel : ViewModel() {
callback: (Resource<EpisodeData>) -> Unit, callback: (Resource<EpisodeData>) -> Unit,
) = ) =
viewModelScope.launch { viewModelScope.launch {
if (_allEpisodes.value?.contains(id) == true) { val localData = loadEpisode(id, data, isCasting)
_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)
}
callback.invoke(localData) 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.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule 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" const val PREFERENCES_NAME: String = "rebuild_preference"
object DataStore { object DataStore {

View file

@ -13,4 +13,27 @@
<!-- Actually fake to make the skip op button the same style --> <!-- Actually fake to make the skip op button the same style -->
<item>@id/cast_button_type_forward_30_seconds</item> <item>@id/cast_button_type_forward_30_seconds</item>
</array> </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> </resources>

View file

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