mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
viewmodel dialogs
This commit is contained in:
parent
64ea5e2f4b
commit
a99713fe0c
17 changed files with 655 additions and 192 deletions
|
@ -1114,6 +1114,7 @@ data class NextAiring(
|
|||
data class SeasonData(
|
||||
val season: Int,
|
||||
val name: String? = null,
|
||||
val displaySeason : Int? = null, // will use season if null
|
||||
)
|
||||
|
||||
interface EpisodeResponse {
|
||||
|
|
|
@ -332,6 +332,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
if (str.contains(appString)) {
|
||||
for (api in OAuth2Apis) {
|
||||
if (str.contains("/${api.redirectUrl}")) {
|
||||
val activity = this
|
||||
ioSafe {
|
||||
Log.i(TAG, "handleAppIntent $str")
|
||||
val isSuccessful = api.handleRedirect(str)
|
||||
|
@ -342,10 +343,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
Log.i(TAG, "failed to authenticate ${api.name}")
|
||||
}
|
||||
|
||||
this.runOnUiThread {
|
||||
activity.runOnUiThread {
|
||||
try {
|
||||
showToast(
|
||||
this,
|
||||
activity,
|
||||
getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format(
|
||||
api.name
|
||||
)
|
||||
|
|
|
@ -54,7 +54,7 @@ class GogoanimeProvider : MainAPI() {
|
|||
secretKeyString: String,
|
||||
encrypt: Boolean = true
|
||||
): String {
|
||||
println("IV: $iv, Key: $secretKeyString, encrypt: $encrypt, Message: $string")
|
||||
//println("IV: $iv, Key: $secretKeyString, encrypt: $encrypt, Message: $string")
|
||||
val ivParameterSpec = IvParameterSpec(iv.toByteArray())
|
||||
val secretKey = SecretKeySpec(secretKeyString.toByteArray(), "AES")
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
|
|
|
@ -51,6 +51,32 @@ fun <T> LifecycleOwner.observeDirectly(liveData: LiveData<T>, action: (t: T) ->
|
|||
action(currentValue)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> some(value: T?): Some<T> {
|
||||
return if (value == null) {
|
||||
Some.None
|
||||
} else {
|
||||
Some.Success(value)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Some<out T> {
|
||||
data class Success<out T>(val value: T) : Some<T>()
|
||||
object None : Some<Nothing>()
|
||||
|
||||
override fun toString(): String {
|
||||
return when(this) {
|
||||
is None -> "None"
|
||||
is Success -> "Some(${value.toString()})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ResourceSome<out T> {
|
||||
data class Success<out T>(val value: T) : ResourceSome<T>()
|
||||
object None : ResourceSome<Nothing>()
|
||||
data class Loading(val data: Any? = null) : ResourceSome<Nothing>()
|
||||
}
|
||||
|
||||
sealed class Resource<out T> {
|
||||
data class Success<out T>(val value: T) : Resource<T>()
|
||||
data class Failure(
|
||||
|
|
|
@ -125,6 +125,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
private fun loadExtractorJob(extractorLink: ExtractorLink?) {
|
||||
currentVerifyLink?.cancel()
|
||||
|
||||
extractorLink?.let {
|
||||
currentVerifyLink = ioSafe {
|
||||
if (it.extractorData != null) {
|
||||
|
@ -488,7 +489,9 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
.setView(R.layout.player_select_source_and_subs)
|
||||
|
||||
val sourceDialog = sourceBuilder.create()
|
||||
|
||||
selectSourceDialog = sourceDialog
|
||||
|
||||
sourceDialog.show()
|
||||
val providerList = sourceDialog.sort_providers
|
||||
val subtitleList = sourceDialog.sort_subtitles
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.lagradost.cloudstream3.ui.result
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.content.Intent.*
|
||||
import android.content.res.ColorStateList
|
||||
|
@ -15,6 +16,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.AbsListView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isGone
|
||||
|
@ -28,15 +30,13 @@ import com.discord.panels.PanelsChildGestureRegionObserver
|
|||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
import com.google.android.gms.cast.framework.CastState
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
||||
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.mvvm.*
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||
|
@ -54,8 +54,10 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper
|
|||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
|
||||
import com.lagradost.cloudstream3.utils.UIHelper
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||
|
@ -68,6 +70,7 @@ import kotlinx.android.synthetic.main.fragment_trailer.*
|
|||
import kotlinx.android.synthetic.main.result_recommendations.*
|
||||
import kotlinx.android.synthetic.main.result_sync.*
|
||||
import kotlinx.android.synthetic.main.trailer_custom_layout.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
const val START_ACTION_NORMAL = 0
|
||||
const val START_ACTION_RESUME_LATEST = 1
|
||||
|
@ -206,8 +209,6 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
private var updateUIListener: (() -> Unit)? = null
|
||||
}
|
||||
|
||||
private var currentLoadingCount =
|
||||
0 // THIS IS USED TO PREVENT LATE EVENTS, AFTER DISMISS WAS CLICKED
|
||||
private lateinit var viewModel: ResultViewModel2 //by activityViewModels()
|
||||
private lateinit var syncModel: SyncViewModel
|
||||
|
||||
|
@ -418,7 +419,8 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
viewModel.reloadEpisodes()
|
||||
}
|
||||
|
||||
var apiName: String = ""
|
||||
var loadingDialog: Dialog? = null
|
||||
var popupDialog: Dialog? = null
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -466,7 +468,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
// activity?.fixPaddingStatusbar(result_toolbar)
|
||||
|
||||
val url = arguments?.getString(URL_BUNDLE)
|
||||
apiName = arguments?.getString(API_NAME_BUNDLE) ?: return
|
||||
val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return
|
||||
startAction = arguments?.getInt(START_ACTION_BUNDLE) ?: START_ACTION_NORMAL
|
||||
startValue = arguments?.getInt(START_VALUE_BUNDLE)
|
||||
val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE)
|
||||
|
@ -862,16 +864,16 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
*/
|
||||
observe(viewModel.episodes) { episodes ->
|
||||
when (episodes) {
|
||||
is Resource.Failure -> {
|
||||
is ResourceSome.None -> {
|
||||
result_episode_loading?.isVisible = false
|
||||
//result_episodes?.isVisible = false
|
||||
result_episodes?.isVisible = false
|
||||
}
|
||||
is Resource.Loading -> {
|
||||
is ResourceSome.Loading -> {
|
||||
result_episode_loading?.isVisible = true
|
||||
// result_episodes?.isVisible = false
|
||||
result_episodes?.isVisible = false
|
||||
}
|
||||
is Resource.Success -> {
|
||||
//result_episodes?.isVisible = true
|
||||
is ResourceSome.Success -> {
|
||||
result_episodes?.isVisible = true
|
||||
result_episode_loading?.isVisible = false
|
||||
(result_episodes?.adapter as? EpisodeAdapter?)?.updateList(episodes.value)
|
||||
}
|
||||
|
@ -879,7 +881,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
}
|
||||
|
||||
observe(viewModel.selectedSeason) { text ->
|
||||
result_season_button?.setText(text)
|
||||
result_season_button.setText(text)
|
||||
|
||||
// If the season button is visible the result season button will be next focus down
|
||||
if (result_season_button?.isVisible == true)
|
||||
|
@ -901,6 +903,70 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
}
|
||||
}
|
||||
|
||||
observe(viewModel.selectPopup) { popup ->
|
||||
println("POPUPSTATUS:$popup")
|
||||
when (popup) {
|
||||
is Some.Success -> {
|
||||
popupDialog?.dismissSafe(activity)
|
||||
|
||||
popupDialog = activity?.let { act ->
|
||||
val pop = popup.value
|
||||
val options = pop.getOptions(act)
|
||||
val title = pop.getTitle(act)
|
||||
|
||||
act.showBottomDialogInstant(
|
||||
options, title, {
|
||||
popupDialog = null
|
||||
pop.callback(context ?: return@showBottomDialogInstant, null)
|
||||
}, {
|
||||
popupDialog = null
|
||||
pop.callback(context ?: return@showBottomDialogInstant, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
is Some.None -> {
|
||||
popupDialog?.dismissSafe(activity)
|
||||
popupDialog = null
|
||||
}
|
||||
}
|
||||
|
||||
//showBottomDialogInstant
|
||||
}
|
||||
|
||||
observe(viewModel.loadedLinks) { load ->
|
||||
|
||||
when (load) {
|
||||
is Some.Success -> {
|
||||
if(loadingDialog?.isShowing != true) {
|
||||
loadingDialog?.dismissSafe(activity)
|
||||
loadingDialog = null
|
||||
}
|
||||
loadingDialog = loadingDialog ?: context?.let { ctx ->
|
||||
val builder =
|
||||
BottomSheetDialog(ctx)
|
||||
builder.setContentView(R.layout.bottom_loading)
|
||||
builder.setOnDismissListener {
|
||||
loadingDialog = null
|
||||
viewModel.cancelLinks()
|
||||
}
|
||||
//builder.setOnCancelListener {
|
||||
// it?.dismiss()
|
||||
//}
|
||||
builder.setCanceledOnTouchOutside(true)
|
||||
|
||||
builder.show()
|
||||
|
||||
builder
|
||||
}
|
||||
}
|
||||
is Some.None -> {
|
||||
loadingDialog?.dismissSafe(activity)
|
||||
loadingDialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observe(viewModel.selectedRange) { range ->
|
||||
result_episode_select.setText(range)
|
||||
|
||||
|
@ -933,7 +999,8 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
}
|
||||
|
||||
observe(viewModel.rangeSelections) { range ->
|
||||
result_episode_select.setOnClickListener { view ->
|
||||
println("RANGE:$range")
|
||||
result_episode_select?.setOnClickListener { view ->
|
||||
view?.context?.let { ctx ->
|
||||
val names = range
|
||||
.mapNotNull { (text, r) ->
|
||||
|
@ -987,6 +1054,33 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
setRecommendations(recommendations, null)
|
||||
}
|
||||
|
||||
observe(viewModel.movie) { data ->
|
||||
when (data) {
|
||||
is ResourceSome.Success -> {
|
||||
data.value.let { (text, ep) ->
|
||||
result_play_movie.setText(text)
|
||||
result_play_movie?.setOnClickListener {
|
||||
viewModel.handleAction(
|
||||
activity,
|
||||
EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep)
|
||||
)
|
||||
}
|
||||
result_play_movie?.setOnLongClickListener {
|
||||
viewModel.handleAction(
|
||||
activity,
|
||||
EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep)
|
||||
)
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
result_play_movie?.isVisible = false
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observe(viewModel.page) { data ->
|
||||
when (data) {
|
||||
is Resource.Success -> {
|
||||
|
@ -1006,9 +1100,32 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
result_cast_text.setText(d.actorsText)
|
||||
result_next_airing.setText(d.nextAiringEpisode)
|
||||
result_next_airing_time.setText(d.nextAiringDate)
|
||||
|
||||
result_poster.setImage(d.posterImage)
|
||||
result_play_movie.setText(d.playMovieText)
|
||||
|
||||
if (d.posterImage != null && context?.isTrueTvSettings() == false)
|
||||
result_poster_holder?.setOnClickListener {
|
||||
try {
|
||||
context?.let { ctx ->
|
||||
runBlocking {
|
||||
val sourceBuilder = AlertDialog.Builder(ctx)
|
||||
sourceBuilder.setView(R.layout.result_poster)
|
||||
|
||||
val sourceDialog = sourceBuilder.create()
|
||||
sourceDialog.show()
|
||||
|
||||
sourceDialog.findViewById<ImageView?>(R.id.imgPoster)
|
||||
?.apply {
|
||||
setImage(d.posterImage)
|
||||
setOnClickListener {
|
||||
sourceDialog.dismissSafe()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result_cast_items?.isVisible = d.actors != null
|
||||
|
@ -1016,6 +1133,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
updateList(d.actors ?: emptyList())
|
||||
}
|
||||
|
||||
result_open_in_browser?.isGone = d.url.isBlank()
|
||||
result_open_in_browser?.setOnClickListener {
|
||||
val i = Intent(ACTION_VIEW)
|
||||
i.data = Uri.parse(d.url)
|
||||
|
@ -1238,15 +1356,14 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
Kitsu.isEnabled =
|
||||
settingsManager.getBoolean(ctx.getString(R.string.show_kitsu_posters_key), true)
|
||||
|
||||
val tempUrl = url
|
||||
if (tempUrl != null) {
|
||||
if (url != null) {
|
||||
result_reload_connectionerror.setOnClickListener {
|
||||
viewModel.load(tempUrl, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX
|
||||
viewModel.load(url, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX
|
||||
}
|
||||
|
||||
result_reload_connection_open_in_browser?.setOnClickListener {
|
||||
val i = Intent(ACTION_VIEW)
|
||||
i.data = Uri.parse(tempUrl)
|
||||
i.data = Uri.parse(url)
|
||||
try {
|
||||
startActivity(i)
|
||||
} catch (e: Exception) {
|
||||
|
@ -1256,7 +1373,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
|
||||
result_open_in_browser?.setOnClickListener {
|
||||
val i = Intent(ACTION_VIEW)
|
||||
i.data = Uri.parse(tempUrl)
|
||||
i.data = Uri.parse(url)
|
||||
try {
|
||||
startActivity(i)
|
||||
} catch (e: Exception) {
|
||||
|
@ -1267,7 +1384,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
// bloats the navigation on tv
|
||||
if (context?.isTrueTvSettings() == false) {
|
||||
result_meta_site?.setOnClickListener {
|
||||
it.context?.openBrowser(tempUrl)
|
||||
it.context?.openBrowser(url)
|
||||
}
|
||||
result_meta_site?.isFocusable = true
|
||||
} else {
|
||||
|
@ -1276,7 +1393,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
|
||||
if (restart || !viewModel.hasLoaded()) {
|
||||
//viewModel.clear()
|
||||
viewModel.load(tempUrl, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX
|
||||
viewModel.load(url, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,17 +36,16 @@ import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
|
|||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
|
||||
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
@ -86,7 +85,6 @@ data class ResultData(
|
|||
val yearText: UiText?,
|
||||
val nextAiringDate: UiText?,
|
||||
val nextAiringEpisode: UiText?,
|
||||
val playMovieText: UiText?,
|
||||
val plotHeaderText: UiText,
|
||||
)
|
||||
|
||||
|
@ -118,7 +116,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
|
|||
val hours: Long = TimeUnit.SECONDS.toHours(seconds) - days * 24
|
||||
val minute =
|
||||
TimeUnit.SECONDS.toMinutes(seconds) - TimeUnit.SECONDS.toHours(seconds) * 60
|
||||
nextAiringEpisode = when {
|
||||
nextAiringDate = when {
|
||||
days > 0 -> {
|
||||
txt(
|
||||
R.string.next_episode_time_day_format,
|
||||
|
@ -138,11 +136,11 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
|
|||
)
|
||||
else -> null
|
||||
}?.also {
|
||||
nextAiringDate = txt(R.string.next_episode_format, airing.episode)
|
||||
nextAiringEpisode = txt(R.string.next_episode_format, airing.episode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dur = duration
|
||||
return ResultData(
|
||||
syncData = syncData,
|
||||
plotHeaderText = txt(
|
||||
|
@ -151,14 +149,6 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
|
|||
else -> R.string.result_plot
|
||||
}
|
||||
),
|
||||
playMovieText = txt(
|
||||
when (this.type) {
|
||||
TvType.Live -> R.string.play_livestream_button
|
||||
TvType.Torrent -> R.string.play_torrent_button
|
||||
TvType.Movie, TvType.AnimeMovie -> R.string.play_movie_button
|
||||
else -> null
|
||||
}
|
||||
),
|
||||
nextAiringDate = nextAiringDate,
|
||||
nextAiringEpisode = nextAiringEpisode,
|
||||
posterImage = img(
|
||||
|
@ -192,7 +182,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
|
|||
TvType.Live -> R.string.live_singular
|
||||
}
|
||||
),
|
||||
yearText = txt(year),
|
||||
yearText = txt(year?.toString()),
|
||||
apiName = txt(apiName),
|
||||
ratingText = rating?.div(1000f)?.let { txt(R.string.rating_format, it) },
|
||||
vpnText = txt(
|
||||
|
@ -204,7 +194,10 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
|
|||
),
|
||||
metaText =
|
||||
if (repo.providerType == ProviderType.MetaProvider) txt(R.string.provider_info_meta) else null,
|
||||
durationText = txt(R.string.duration_format, duration),
|
||||
durationText = if (dur == null || dur <= 0) null else txt(
|
||||
R.string.duration_format,
|
||||
dur
|
||||
),
|
||||
onGoingText = if (this is EpisodeResponse) {
|
||||
txt(
|
||||
when (showStatus) {
|
||||
|
@ -245,21 +238,43 @@ sealed class SelectPopup {
|
|||
val map: Int?,
|
||||
val callback: (Int?) -> Unit
|
||||
) : SelectPopup()
|
||||
}
|
||||
|
||||
fun SelectPopup.transformResult(context: Context, input: Int?): Int? {
|
||||
if (input == null) return null
|
||||
return when (this) {
|
||||
is SelectArray -> context.resources.getIntArray(map ?: return input).getOrNull(input)
|
||||
?: input
|
||||
is SelectText -> input
|
||||
}
|
||||
fun SelectPopup.callback(context: Context, input: Int?) {
|
||||
val ret = transformResult(context, input)
|
||||
return when (this) {
|
||||
is SelectPopup.SelectArray -> callback(ret)
|
||||
is SelectPopup.SelectText -> callback(ret)
|
||||
}
|
||||
}
|
||||
|
||||
fun SelectPopup.getOptions(context: Context): List<String> {
|
||||
return when (this) {
|
||||
is SelectArray -> context.resources.getStringArray(options).toList()
|
||||
is SelectText -> options.map { it.asString(context) }
|
||||
fun SelectPopup.transformResult(context: Context, input: Int?): Int? {
|
||||
if (input == null) return null
|
||||
return when (this) {
|
||||
is SelectPopup.SelectArray -> context.resources.getIntArray(map ?: return input)
|
||||
.getOrNull(input)
|
||||
?: input
|
||||
is SelectPopup.SelectText -> input
|
||||
}
|
||||
}
|
||||
|
||||
fun SelectPopup.getTitle(context: Context): String {
|
||||
return when (this) {
|
||||
is SelectPopup.SelectArray -> text.asString(context)
|
||||
is SelectPopup.SelectText -> text.asString(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun SelectPopup.getOptions(context: Context): List<String> {
|
||||
return when (this) {
|
||||
is SelectPopup.SelectArray -> {
|
||||
val cmap = this.map?.let { context.resources.getIntArray(it) }
|
||||
context.resources.getStringArray(options).toList().filterIndexed { index, s ->
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
is SelectPopup.SelectText -> options.map { it.asString(context) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,6 +289,8 @@ class ResultViewModel2 : ViewModel() {
|
|||
/** map<dub, map<season, List<episode>>> */
|
||||
private var currentEpisodes: Map<EpisodeIndexer, List<ResultEpisode>> = mapOf()
|
||||
private var currentRanges: Map<EpisodeIndexer, List<EpisodeRange>> = mapOf()
|
||||
private var currentSeasons: Set<Int> = setOf()
|
||||
private var currentDubStatus: Set<DubStatus> = setOf()
|
||||
private var currentMeta: SyncAPI.SyncResult? = null
|
||||
private var currentSync: Map<String, String>? = null
|
||||
private var currentIndex: EpisodeIndexer? = null
|
||||
|
@ -294,13 +311,17 @@ class ResultViewModel2 : ViewModel() {
|
|||
MutableLiveData(Resource.Loading())
|
||||
val page: LiveData<Resource<ResultData>> = _page
|
||||
|
||||
private val _episodes: MutableLiveData<Resource<List<ResultEpisode>>> =
|
||||
MutableLiveData(Resource.Loading())
|
||||
val episodes: LiveData<Resource<List<ResultEpisode>>> = _episodes
|
||||
private val _episodes: MutableLiveData<ResourceSome<List<ResultEpisode>>> =
|
||||
MutableLiveData(ResourceSome.Loading())
|
||||
val episodes: LiveData<ResourceSome<List<ResultEpisode>>> = _episodes
|
||||
|
||||
private val _episodesCountText: MutableLiveData<UiText?> =
|
||||
MutableLiveData(null)
|
||||
val episodesCountText: LiveData<UiText?> = _episodesCountText
|
||||
private val _movie: MutableLiveData<ResourceSome<Pair<UiText, ResultEpisode>>> =
|
||||
MutableLiveData(ResourceSome.None)
|
||||
val movie: LiveData<ResourceSome<Pair<UiText, ResultEpisode>>> = _movie
|
||||
|
||||
private val _episodesCountText: MutableLiveData<Some<UiText>> =
|
||||
MutableLiveData(Some.None)
|
||||
val episodesCountText: LiveData<Some<UiText>> = _episodesCountText
|
||||
|
||||
private val _trailers: MutableLiveData<List<TrailerData>> = MutableLiveData(mutableListOf())
|
||||
val trailers: LiveData<List<TrailerData>> = _trailers
|
||||
|
@ -318,24 +339,23 @@ class ResultViewModel2 : ViewModel() {
|
|||
MutableLiveData(emptyList())
|
||||
val seasonSelections: LiveData<List<Pair<UiText?, Int>>> = _seasonSelections
|
||||
|
||||
|
||||
private val _recommendations: MutableLiveData<List<SearchResponse>> =
|
||||
MutableLiveData(emptyList())
|
||||
val recommendations: LiveData<List<SearchResponse>> = _recommendations
|
||||
|
||||
private val _selectedRange: MutableLiveData<UiText?> =
|
||||
MutableLiveData(null)
|
||||
val selectedRange: LiveData<UiText?> = _selectedRange
|
||||
private val _selectedRange: MutableLiveData<Some<UiText>> =
|
||||
MutableLiveData(Some.None)
|
||||
val selectedRange: LiveData<Some<UiText>> = _selectedRange
|
||||
|
||||
private val _selectedSeason: MutableLiveData<UiText?> =
|
||||
MutableLiveData(null)
|
||||
val selectedSeason: LiveData<UiText?> = _selectedSeason
|
||||
private val _selectedSeason: MutableLiveData<Some<UiText>> =
|
||||
MutableLiveData(Some.None)
|
||||
val selectedSeason: LiveData<Some<UiText>> = _selectedSeason
|
||||
|
||||
private val _selectedDubStatus: MutableLiveData<UiText?> = MutableLiveData(null)
|
||||
val selectedDubStatus: LiveData<UiText?> = _selectedDubStatus
|
||||
private val _selectedDubStatus: MutableLiveData<Some<UiText>> = MutableLiveData(Some.None)
|
||||
val selectedDubStatus: LiveData<Some<UiText>> = _selectedDubStatus
|
||||
|
||||
private val _loadedLinks: MutableLiveData<LinkProgress?> = MutableLiveData(null)
|
||||
val loadedLinks: LiveData<LinkProgress?> = _loadedLinks
|
||||
private val _loadedLinks: MutableLiveData<Some<LinkProgress>> = MutableLiveData(Some.None)
|
||||
val loadedLinks: LiveData<Some<LinkProgress>> = _loadedLinks
|
||||
|
||||
companion object {
|
||||
const val TAG = "RVM2"
|
||||
|
@ -680,8 +700,8 @@ class ResultViewModel2 : ViewModel() {
|
|||
private val _watchStatus: MutableLiveData<WatchType> = MutableLiveData(WatchType.NONE)
|
||||
val watchStatus: LiveData<WatchType> get() = _watchStatus
|
||||
|
||||
private val _selectPopup: MutableLiveData<SelectPopup?> = MutableLiveData(null)
|
||||
val selectPopup: LiveData<SelectPopup?> get() = _selectPopup
|
||||
private val _selectPopup: MutableLiveData<Some<SelectPopup>> = MutableLiveData(Some.None)
|
||||
val selectPopup: LiveData<Some<SelectPopup>> get() = _selectPopup
|
||||
|
||||
fun updateWatchStatus(status: WatchType) {
|
||||
val currentId = currentId ?: return
|
||||
|
@ -707,14 +727,15 @@ class ResultViewModel2 : ViewModel() {
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun startChromecast(
|
||||
private fun startChromecast(
|
||||
activity: Activity?,
|
||||
result: ResultEpisode,
|
||||
isVisible: Boolean = true
|
||||
) {
|
||||
if (activity == null) return
|
||||
val data = loadLinks(result, isVisible = isVisible, isCasting = true)
|
||||
startChromecast(activity, result, data.links, data.subs, 0)
|
||||
loadLinks(result, isVisible = isVisible, isCasting = true) { data ->
|
||||
startChromecast(activity, result, data.links, data.subs, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startChromecast(
|
||||
|
@ -742,51 +763,100 @@ class ResultViewModel2 : ViewModel() {
|
|||
)
|
||||
}
|
||||
|
||||
private val popupCallback: ((Int) -> Unit)? = null
|
||||
|
||||
fun cancelLinks() {
|
||||
println("called::cancelLinks")
|
||||
currentLoadLinkJob?.cancel()
|
||||
_loadedLinks.postValue(null)
|
||||
currentLoadLinkJob = null
|
||||
_loadedLinks.postValue(Some.None)
|
||||
}
|
||||
|
||||
private fun postPopup(text: UiText, options: List<UiText>, callback: suspend (Int?) -> Unit) {
|
||||
_selectPopup.postValue(
|
||||
some(SelectPopup.SelectText(
|
||||
text,
|
||||
options
|
||||
) { value ->
|
||||
viewModelScope.launch {
|
||||
_selectPopup.postValue(Some.None)
|
||||
callback.invoke(value)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private fun postPopup(
|
||||
text: UiText,
|
||||
options: Int,
|
||||
values: Int,
|
||||
callback: suspend (Int?) -> Unit
|
||||
) {
|
||||
_selectPopup.postValue(
|
||||
some(SelectPopup.SelectArray(
|
||||
text,
|
||||
options,
|
||||
values
|
||||
) { value ->
|
||||
viewModelScope.launch {
|
||||
_selectPopup.value = Some.None
|
||||
callback.invoke(value)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
fun loadLinks(
|
||||
result: ResultEpisode,
|
||||
isVisible: Boolean,
|
||||
isCasting: Boolean,
|
||||
clearCache: Boolean = false,
|
||||
work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit)
|
||||
) {
|
||||
currentLoadLinkJob?.cancel()
|
||||
currentLoadLinkJob = ioSafe {
|
||||
val links = loadLinks(
|
||||
result,
|
||||
isVisible = isVisible,
|
||||
isCasting = isCasting,
|
||||
clearCache = clearCache
|
||||
)
|
||||
if (!this.isActive) return@ioSafe
|
||||
work(links)
|
||||
}
|
||||
}
|
||||
|
||||
private var currentLoadLinkJob: Job? = null
|
||||
private suspend fun acquireSingleLink(
|
||||
private fun acquireSingleLink(
|
||||
result: ResultEpisode,
|
||||
isCasting: Boolean,
|
||||
text: UiText,
|
||||
callback: (Pair<LinkLoadingResult, Int>) -> Unit,
|
||||
) {
|
||||
currentLoadLinkJob = viewModelScope.launch {
|
||||
val links = loadLinks(result, isVisible = true, isCasting = isCasting)
|
||||
|
||||
_selectPopup.postValue(
|
||||
SelectPopup.SelectText(
|
||||
text,
|
||||
links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) {
|
||||
callback.invoke(links to (it ?: return@SelectText))
|
||||
})
|
||||
loadLinks(result, isVisible = true, isCasting = isCasting) { links ->
|
||||
postPopup(
|
||||
text,
|
||||
links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) {
|
||||
callback.invoke(links to (it ?: return@postPopup))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun acquireSingleSubtitle(
|
||||
private fun acquireSingleSubtitle(
|
||||
result: ResultEpisode,
|
||||
isCasting: Boolean,
|
||||
text: UiText,
|
||||
callback: (Pair<LinkLoadingResult, Int>) -> Unit,
|
||||
) {
|
||||
currentLoadLinkJob = viewModelScope.launch {
|
||||
val links = loadLinks(result, isVisible = true, isCasting = isCasting)
|
||||
|
||||
_selectPopup.postValue(
|
||||
SelectPopup.SelectText(
|
||||
text,
|
||||
links.subs.map { txt(it.name) }) {
|
||||
callback.invoke(links to (it ?: return@SelectText))
|
||||
})
|
||||
loadLinks(result, isVisible = true, isCasting = isCasting) { links ->
|
||||
postPopup(
|
||||
text,
|
||||
links.subs.map { txt(it.name) })
|
||||
{
|
||||
callback.invoke(links to (it ?: return@postPopup))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadLinks(
|
||||
suspend fun CoroutineScope.loadLinks(
|
||||
result: ResultEpisode,
|
||||
isVisible: Boolean,
|
||||
isCasting: Boolean,
|
||||
|
@ -797,11 +867,12 @@ class ResultViewModel2 : ViewModel() {
|
|||
val links: MutableSet<ExtractorLink> = mutableSetOf()
|
||||
val subs: MutableSet<SubtitleData> = mutableSetOf()
|
||||
fun updatePage() {
|
||||
if (isVisible) {
|
||||
_loadedLinks.postValue(LinkProgress(links.size, subs.size))
|
||||
if (isVisible && isActive) {
|
||||
_loadedLinks.postValue(some(LinkProgress(links.size, subs.size)))
|
||||
}
|
||||
}
|
||||
try {
|
||||
updatePage()
|
||||
tempGenerator.generateLinks(clearCache, isCasting, { (link, _) ->
|
||||
if (link != null) {
|
||||
links += link
|
||||
|
@ -814,7 +885,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
} finally {
|
||||
_loadedLinks.postValue(null)
|
||||
_loadedLinks.postValue(Some.None)
|
||||
}
|
||||
|
||||
return LinkLoadingResult(sortUrls(links), sortSubs(subs))
|
||||
|
@ -884,20 +955,22 @@ class ResultViewModel2 : ViewModel() {
|
|||
private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) {
|
||||
when (click.action) {
|
||||
ACTION_SHOW_OPTIONS -> {
|
||||
_selectPopup.postValue(
|
||||
SelectPopup.SelectArray(
|
||||
txt(""), // TODO FIX
|
||||
R.array.episode_long_click_options,
|
||||
R.array.episode_long_click_options_values
|
||||
) { result ->
|
||||
if (result == null) return@SelectArray
|
||||
viewModelScope.launch {
|
||||
handleEpisodeClickEvent(
|
||||
activity,
|
||||
click.copy(action = result)
|
||||
)
|
||||
}
|
||||
})
|
||||
postPopup(
|
||||
txt(
|
||||
activity?.getNameFull(
|
||||
click.data.name,
|
||||
click.data.episode,
|
||||
click.data.season
|
||||
) ?: ""
|
||||
), // TODO FIX
|
||||
R.array.episode_long_click_options,
|
||||
R.array.episode_long_click_options_values
|
||||
) { result ->
|
||||
handleEpisodeClickEvent(
|
||||
activity,
|
||||
click.copy(action = result ?: return@postPopup)
|
||||
)
|
||||
}
|
||||
}
|
||||
ACTION_CLICK_DEFAULT -> {
|
||||
activity?.let { ctx ->
|
||||
|
@ -986,7 +1059,14 @@ class ResultViewModel2 : ViewModel() {
|
|||
}
|
||||
}
|
||||
ACTION_RELOAD_EPISODE -> {
|
||||
loadLinks(click.data, isVisible = false, isCasting = false, clearCache = true)
|
||||
ioSafe {
|
||||
loadLinks(
|
||||
click.data,
|
||||
isVisible = false,
|
||||
isCasting = false,
|
||||
clearCache = true
|
||||
)
|
||||
}
|
||||
}
|
||||
ACTION_CHROME_CAST_MIRROR -> {
|
||||
acquireSingleLink(
|
||||
|
@ -1030,8 +1110,12 @@ class ResultViewModel2 : ViewModel() {
|
|||
startChromecast(activity, click.data)
|
||||
}
|
||||
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> {
|
||||
currentLoadLinkJob = viewModelScope.launch {
|
||||
playWithVlc(activity, loadLinks(click.data, true, true), click.data.id)
|
||||
loadLinks(click.data, isVisible = true, isCasting = true) { links ->
|
||||
playWithVlc(
|
||||
activity,
|
||||
links,
|
||||
click.data.id
|
||||
)
|
||||
}
|
||||
}
|
||||
ACTION_PLAY_EPISODE_IN_PLAYER -> {
|
||||
|
@ -1176,6 +1260,13 @@ class ResultViewModel2 : ViewModel() {
|
|||
postEpisodeRange(currentIndex?.copy(season = season), currentRange)
|
||||
}
|
||||
|
||||
private fun getMovie(): ResultEpisode? {
|
||||
return currentEpisodes.entries.firstOrNull()?.value?.firstOrNull()?.let { ep ->
|
||||
val posDur = DataStoreHelper.getViewPos(ep.id)
|
||||
ep.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEpisodes(indexer: EpisodeIndexer, range: EpisodeRange): List<ResultEpisode> {
|
||||
val startIndex = range.startIndex
|
||||
val length = range.length
|
||||
|
@ -1192,15 +1283,50 @@ class ResultViewModel2 : ViewModel() {
|
|||
?: emptyList()
|
||||
}
|
||||
|
||||
private fun postMovie() {
|
||||
val response = currentResponse
|
||||
_episodes.postValue(ResourceSome.None)
|
||||
|
||||
if (response == null) {
|
||||
_movie.postValue(ResourceSome.None)
|
||||
return
|
||||
}
|
||||
|
||||
val text = txt(
|
||||
when (response.type) {
|
||||
TvType.Torrent -> R.string.play_torrent_button
|
||||
else -> {
|
||||
if (response.type.isLiveStream())
|
||||
R.string.play_livestream_button
|
||||
else if (response.type.isMovieType()) // this wont break compatibility as you only need to override isMovieType
|
||||
R.string.play_movie_button
|
||||
else null
|
||||
}
|
||||
}
|
||||
)
|
||||
val data = getMovie()
|
||||
_episodes.postValue(ResourceSome.None)
|
||||
if (text == null || data == null) {
|
||||
_movie.postValue(ResourceSome.None)
|
||||
} else {
|
||||
_movie.postValue(ResourceSome.Success(text to data))
|
||||
}
|
||||
}
|
||||
|
||||
fun reloadEpisodes() {
|
||||
_episodes.postValue(
|
||||
Resource.Success(
|
||||
getEpisodes(
|
||||
currentIndex ?: return,
|
||||
currentRange ?: return
|
||||
if (currentResponse?.isMovie() == true) {
|
||||
postMovie()
|
||||
} else {
|
||||
_episodes.postValue(
|
||||
ResourceSome.Success(
|
||||
getEpisodes(
|
||||
currentIndex ?: return,
|
||||
currentRange ?: return
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
_movie.postValue(ResourceSome.None)
|
||||
}
|
||||
}
|
||||
|
||||
private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) {
|
||||
|
@ -1208,45 +1334,79 @@ class ResultViewModel2 : ViewModel() {
|
|||
return
|
||||
}
|
||||
|
||||
val size = currentEpisodes[indexer]?.size
|
||||
|
||||
_episodesCountText.postValue(
|
||||
txt(
|
||||
R.string.episode_format,
|
||||
txt(if (size == 1) R.string.episode else R.string.episodes),
|
||||
size
|
||||
)
|
||||
)
|
||||
|
||||
val episodes = currentEpisodes[indexer]
|
||||
val ranges = currentRanges[indexer]
|
||||
val size = episodes?.size
|
||||
val isMovie = currentResponse?.isMovie() == true
|
||||
currentIndex = indexer
|
||||
currentRange = range
|
||||
|
||||
|
||||
_rangeSelections.postValue(ranges?.map { r ->
|
||||
val text = txt(R.string.episodes_range, r.startEpisode, r.endEpisode)
|
||||
text to r
|
||||
} ?: emptyList())
|
||||
|
||||
_episodesCountText.postValue(
|
||||
some(
|
||||
if (isMovie) null else
|
||||
txt(
|
||||
R.string.episode_format,
|
||||
size,
|
||||
txt(if (size == 1) R.string.episode else R.string.episodes),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
_selectedSeason.postValue(
|
||||
when (indexer.season) {
|
||||
0 -> txt(R.string.no_season)
|
||||
else -> txt(R.string.season_format, R.string.season, indexer.season) //TODO FIX
|
||||
}
|
||||
some(
|
||||
if (isMovie || currentSeasons.size <= 1) null else
|
||||
when (indexer.season) {
|
||||
0 -> txt(R.string.no_season)
|
||||
else -> txt(
|
||||
R.string.season_format,
|
||||
txt(R.string.season),
|
||||
indexer.season
|
||||
) //TODO FIX DISPLAYNAME
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
_selectedRange.postValue(
|
||||
if ((currentRanges[indexer]?.size ?: 0) > 1) {
|
||||
txt(R.string.episodes_range, range.startEpisode, range.endEpisode)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
some(
|
||||
if (isMovie) null else if ((currentRanges[indexer]?.size ?: 0) > 1) {
|
||||
txt(R.string.episodes_range, range.startEpisode, range.endEpisode)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
)
|
||||
_selectedDubStatus.postValue(
|
||||
some(
|
||||
if (isMovie || currentDubStatus.size <= 1) null else
|
||||
txt(indexer.dubStatus)
|
||||
)
|
||||
)
|
||||
_selectedDubStatus.postValue(txt(indexer.dubStatus))
|
||||
|
||||
//TODO SET KEYS
|
||||
preferStartEpisode = range.startEpisode
|
||||
preferStartSeason = indexer.season
|
||||
preferDubStatus = indexer.dubStatus
|
||||
|
||||
generator = currentEpisodes[indexer]?.let { list ->
|
||||
RepoLinkGenerator(list)
|
||||
generator = if (isMovie) {
|
||||
getMovie()?.let { RepoLinkGenerator(listOf(it)) }
|
||||
} else {
|
||||
episodes?.let { list ->
|
||||
RepoLinkGenerator(list)
|
||||
}
|
||||
}
|
||||
|
||||
val ret = getEpisodes(indexer, range)
|
||||
_episodes.postValue(Resource.Success(ret))
|
||||
if (isMovie) {
|
||||
postMovie()
|
||||
} else {
|
||||
val ret = getEpisodes(indexer, range)
|
||||
_episodes.postValue(ResourceSome.Success(ret))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun postSuccessful(
|
||||
|
@ -1262,11 +1422,13 @@ class ResultViewModel2 : ViewModel() {
|
|||
}
|
||||
|
||||
private suspend fun postEpisodes(loadResponse: LoadResponse, updateFillers: Boolean) {
|
||||
_episodes.postValue(Resource.Loading())
|
||||
_episodes.postValue(ResourceSome.Loading())
|
||||
|
||||
val mainId = loadResponse.getId()
|
||||
currentId = mainId
|
||||
|
||||
_watchStatus.postValue(getResultWatchState(mainId))
|
||||
|
||||
if (updateFillers && loadResponse is AnimeLoadResponse) {
|
||||
updateFillers(loadResponse.name)
|
||||
}
|
||||
|
@ -1425,10 +1587,30 @@ class ResultViewModel2 : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
val seasonsSelection = mutableSetOf<Int>()
|
||||
val dubSelection = mutableSetOf<DubStatus>()
|
||||
allEpisodes.keys.forEach { key ->
|
||||
seasonsSelection += key.season
|
||||
dubSelection += key.dubStatus
|
||||
}
|
||||
currentDubStatus = dubSelection
|
||||
currentSeasons = seasonsSelection
|
||||
_dubSubSelections.postValue(dubSelection.map { txt(it) to it })
|
||||
if (loadResponse is EpisodeResponse) {
|
||||
_seasonSelections.postValue(seasonsSelection.map { seasonNumber ->
|
||||
val name =
|
||||
loadResponse.seasonNames?.firstOrNull { it.season == seasonNumber }?.name?.let { seasonData ->
|
||||
txt(seasonData)
|
||||
} ?: txt(R.string.season_format, txt(R.string.season), seasonNumber)
|
||||
name to seasonNumber
|
||||
})
|
||||
}
|
||||
|
||||
currentEpisodes = allEpisodes
|
||||
val ranges = getRanges(allEpisodes)
|
||||
currentRanges = ranges
|
||||
|
||||
|
||||
// this takes the indexer most preferable by the user given the current sorting
|
||||
val min = ranges.keys.minByOrNull { index ->
|
||||
kotlin.math.abs(
|
||||
|
@ -1464,7 +1646,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
) =
|
||||
viewModelScope.launch {
|
||||
_page.postValue(Resource.Loading(url))
|
||||
_episodes.postValue(Resource.Loading(url))
|
||||
_episodes.postValue(ResourceSome.Loading())
|
||||
|
||||
preferDubStatus = dubStatus
|
||||
currentShowFillers = showFillers
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.annotation.DrawableRes
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.lagradost.cloudstream3.mvvm.Some
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
|
@ -152,3 +153,11 @@ fun TextView?.setTextHtml(text: UiText?) {
|
|||
this.text = str.html()
|
||||
}
|
||||
}
|
||||
|
||||
fun TextView?.setTextHtml(text: Some<UiText>) {
|
||||
setTextHtml(if(text is Some.Success) text.value else null)
|
||||
}
|
||||
|
||||
fun TextView?.setText(text: Some<UiText>) {
|
||||
setText(if(text is Some.Success) text.value else null)
|
||||
}
|
|
@ -179,21 +179,21 @@ object AppUtils {
|
|||
@WorkerThread
|
||||
fun Context.addProgramsToContinueWatching(data: List<DataStoreHelper.ResumeWatchingResult>) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
|
||||
val context = this
|
||||
ioSafe {
|
||||
data.forEach { episodeInfo ->
|
||||
try {
|
||||
val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, this)
|
||||
val nextProgram = buildWatchNextProgramUri(this, episodeInfo)
|
||||
val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, context)
|
||||
val nextProgram = buildWatchNextProgramUri(context, episodeInfo)
|
||||
|
||||
// If the program is already in the Watch Next row, update it
|
||||
if (program != null && id != null) {
|
||||
PreviewChannelHelper(this).updateWatchNextProgram(
|
||||
PreviewChannelHelper(context).updateWatchNextProgram(
|
||||
nextProgram,
|
||||
id,
|
||||
)
|
||||
} else {
|
||||
PreviewChannelHelper(this)
|
||||
PreviewChannelHelper(context)
|
||||
.publishWatchNextProgram(nextProgram)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
|
|
@ -12,7 +12,7 @@ object Coroutines {
|
|||
}
|
||||
}
|
||||
|
||||
fun ioSafe(work: suspend (() -> Unit)): Job {
|
||||
fun ioSafe(work: suspend (CoroutineScope.() -> Unit)): Job {
|
||||
return CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
work()
|
||||
|
@ -22,7 +22,7 @@ object Coroutines {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun <T> ioWork(work: suspend (() -> T)): T {
|
||||
suspend fun <T> ioWork(work: suspend (CoroutineScope.() -> T)): T {
|
||||
return withContext(Dispatchers.IO) {
|
||||
work()
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSet
|
|||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
import kotlinx.android.synthetic.main.add_account_input.*
|
||||
import kotlinx.android.synthetic.main.add_account_input.text1
|
||||
import kotlinx.android.synthetic.main.bottom_selection_dialog_direct.*
|
||||
|
||||
object SingleSelectionHelper {
|
||||
fun Activity?.showOptionSelectStringRes(
|
||||
|
@ -21,7 +24,7 @@ object SingleSelectionHelper {
|
|||
tvOptions: List<Int> = listOf(),
|
||||
callback: (Pair<Boolean, Int>) -> Unit
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
this.showOptionSelect(
|
||||
view,
|
||||
|
@ -39,7 +42,7 @@ object SingleSelectionHelper {
|
|||
tvOptions: List<String>,
|
||||
callback: (Pair<Boolean, Int>) -> Unit
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
if (this.isTvSettings()) {
|
||||
val builder =
|
||||
|
@ -86,42 +89,44 @@ object SingleSelectionHelper {
|
|||
showApply: Boolean,
|
||||
isMultiSelect: Boolean,
|
||||
callback: (List<Int>) -> Unit,
|
||||
dismissCallback: () -> Unit
|
||||
dismissCallback: () -> Unit,
|
||||
itemLayout: Int = R.layout.sort_bottom_single_choice
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
val realShowApply = showApply || isMultiSelect
|
||||
val listView = dialog.findViewById<ListView>(R.id.listview1)!!
|
||||
val textView = dialog.findViewById<TextView>(R.id.text1)!!
|
||||
val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!!
|
||||
val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!!
|
||||
val applyHolder = dialog.findViewById<LinearLayout>(R.id.apply_btt_holder)!!
|
||||
val listView = dialog.listview1//.findViewById<ListView>(R.id.listview1)!!
|
||||
val textView = dialog.text1//.findViewById<TextView>(R.id.text1)!!
|
||||
val applyButton = dialog.apply_btt//.findViewById<TextView>(R.id.apply_btt)
|
||||
val cancelButton = dialog.cancel_btt//findViewById<TextView>(R.id.cancel_btt)
|
||||
val applyHolder = dialog.apply_btt_holder//.findViewById<LinearLayout>(R.id.apply_btt_holder)
|
||||
|
||||
applyHolder.isVisible = realShowApply
|
||||
applyHolder?.isVisible = realShowApply
|
||||
if (!realShowApply) {
|
||||
val params = listView.layoutParams as LinearLayout.LayoutParams
|
||||
params.setMargins(listView.marginLeft, listView.marginTop, listView.marginRight, 0)
|
||||
listView.layoutParams = params
|
||||
}
|
||||
|
||||
textView.text = name
|
||||
textView?.text = name
|
||||
textView?.isGone = name.isBlank()
|
||||
|
||||
val arrayAdapter = ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice)
|
||||
val arrayAdapter = ArrayAdapter<String>(this, itemLayout)
|
||||
arrayAdapter.addAll(items)
|
||||
|
||||
listView.adapter = arrayAdapter
|
||||
listView?.adapter = arrayAdapter
|
||||
if (isMultiSelect) {
|
||||
listView.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
|
||||
listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
|
||||
} else {
|
||||
listView.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||
listView?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||
}
|
||||
|
||||
for (select in selectedIndex) {
|
||||
listView.setItemChecked(select, true)
|
||||
listView?.setItemChecked(select, true)
|
||||
}
|
||||
|
||||
selectedIndex.minOrNull()?.let {
|
||||
listView.setSelection(it)
|
||||
listView?.setSelection(it)
|
||||
}
|
||||
|
||||
// var lastSelectedIndex = if(selectedIndex.isNotEmpty()) selectedIndex.first() else -1
|
||||
|
@ -130,7 +135,7 @@ object SingleSelectionHelper {
|
|||
dismissCallback.invoke()
|
||||
}
|
||||
|
||||
listView.setOnItemClickListener { _, _, which, _ ->
|
||||
listView?.setOnItemClickListener { _, _, which, _ ->
|
||||
// lastSelectedIndex = which
|
||||
if (realShowApply) {
|
||||
if (!isMultiSelect) {
|
||||
|
@ -142,7 +147,7 @@ object SingleSelectionHelper {
|
|||
}
|
||||
}
|
||||
if (realShowApply) {
|
||||
applyButton.setOnClickListener {
|
||||
applyButton?.setOnClickListener {
|
||||
val list = ArrayList<Int>()
|
||||
for (index in 0 until listView.count) {
|
||||
if (listView.checkedItemPositions[index])
|
||||
|
@ -151,7 +156,7 @@ object SingleSelectionHelper {
|
|||
callback.invoke(list)
|
||||
dialog.dismissSafe(this)
|
||||
}
|
||||
cancelButton.setOnClickListener {
|
||||
cancelButton?.setOnClickListener {
|
||||
dialog.dismissSafe(this)
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +171,7 @@ object SingleSelectionHelper {
|
|||
callback: (String) -> Unit,
|
||||
dismissCallback: () -> Unit
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
val inputView = dialog.findViewById<EditText>(R.id.nginx_text_input)!!
|
||||
val textView = dialog.findViewById<TextView>(R.id.text1)!!
|
||||
|
@ -205,7 +210,7 @@ object SingleSelectionHelper {
|
|||
dismissCallback: () -> Unit,
|
||||
callback: (List<Int>) -> Unit,
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
val builder =
|
||||
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
||||
|
@ -224,7 +229,7 @@ object SingleSelectionHelper {
|
|||
dismissCallback: () -> Unit,
|
||||
callback: (Int) -> Unit,
|
||||
) {
|
||||
if(this == null) return
|
||||
if (this == null) return
|
||||
|
||||
val builder =
|
||||
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
||||
|
@ -271,6 +276,31 @@ object SingleSelectionHelper {
|
|||
)
|
||||
}
|
||||
|
||||
fun Activity.showBottomDialogInstant(
|
||||
items: List<String>,
|
||||
name: String,
|
||||
dismissCallback: () -> Unit,
|
||||
callback: (Int) -> Unit,
|
||||
): BottomSheetDialog {
|
||||
val builder =
|
||||
BottomSheetDialog(this)
|
||||
builder.setContentView(R.layout.bottom_selection_dialog_direct)
|
||||
|
||||
builder.show()
|
||||
showDialog(
|
||||
builder,
|
||||
items,
|
||||
listOf(),
|
||||
name,
|
||||
showApply = false,
|
||||
isMultiSelect = false,
|
||||
callback = { if (it.isNotEmpty()) callback.invoke(it.first()) },
|
||||
dismissCallback = dismissCallback,
|
||||
itemLayout = R.layout.sort_bottom_single_choice_no_checkmark
|
||||
)
|
||||
return builder
|
||||
}
|
||||
|
||||
fun Activity.showNginxTextInputDialog(
|
||||
name: String,
|
||||
value: String,
|
||||
|
|
33
app/src/main/res/layout/bottom_loading.xml
Normal file
33
app/src/main/res/layout/bottom_loading.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="20sp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
android:text="@string/loading_chromecast"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:layout_marginBottom="-6.5dp"
|
||||
android:indeterminate="true"
|
||||
style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminateTint="?attr/colorPrimary"
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:progressTint="?attr/colorPrimary"
|
||||
android:layout_height="15dp">
|
||||
</androidx.core.widget.ContentLoadingProgressBar>
|
||||
</LinearLayout>
|
34
app/src/main/res/layout/bottom_selection_dialog_direct.xml
Normal file
34
app/src/main/res/layout/bottom_selection_dialog_direct.xml
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?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="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="20sp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
tools:text="Test"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<ListView
|
||||
android:nextFocusRight="@id/cancel_btt"
|
||||
android:nextFocusLeft="@id/apply_btt"
|
||||
|
||||
android:id="@+id/listview1"
|
||||
android:layout_marginBottom="60dp"
|
||||
android:paddingTop="10dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
tools:listitem="@layout/sort_bottom_single_choice_no_checkmark"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_rowWeight="1" />
|
||||
</LinearLayout>
|
|
@ -843,7 +843,6 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/result_next_airing_holder"
|
||||
android:layout_gravity="start"
|
||||
android:paddingBottom="15dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
android:scaleType="fitCenter"
|
||||
android:adjustViewBounds="true"
|
||||
android:src="@drawable/default_cover"
|
||||
android:background="#fffff0"
|
||||
android:background="?attr/primaryGrayBackground"
|
||||
android:contentDescription="@string/poster_image" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,22 @@
|
|||
<!--<CheckedTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/listPreferredItemHeightSmall"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="?attr/textColor"
|
||||
tools:text="Example Text"
|
||||
android:background="?attr/bitDarkerGrayBackground"
|
||||
android:checkMark="?android:attr/listChoiceIndicatorSingle"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"/>
|
||||
-->
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/NoCheckLabel"
|
||||
tools:text="hello"
|
||||
android:textStyle="normal"
|
||||
android:textColor="?attr/textColor"
|
||||
android:id="@android:id/text1" />
|
|
@ -448,7 +448,15 @@
|
|||
<item name="android:textColor">?attr/textColor</item>
|
||||
</style>
|
||||
|
||||
<style name="CheckLabel" parent="@style/AppTextViewStyle">
|
||||
<style name="CheckLabel" parent="@style/NoCheckLabel">
|
||||
|
||||
<!-- <item name="drawableTint">@color/check_selection_color</item>-->
|
||||
<!-- Set color in the drawable instead of tint to allow multiple drawables-->
|
||||
<item name="android:checkMark">?android:attr/listChoiceIndicatorSingle</item>
|
||||
<item name="drawableStartCompat">@drawable/ic_baseline_check_24_listview</item>
|
||||
</style>
|
||||
|
||||
<style name="NoCheckLabel" parent="@style/AppTextViewStyle">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item>
|
||||
|
@ -458,15 +466,13 @@
|
|||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:paddingStart">12dp</item>
|
||||
<item name="android:paddingEnd">12dp</item>
|
||||
<item name="android:checkMark">?android:attr/listChoiceIndicatorSingle</item>
|
||||
<item name="android:ellipsize">marquee</item>
|
||||
<item name="android:foreground">?attr/selectableItemBackgroundBorderless</item>
|
||||
<item name="android:drawablePadding">20dp</item>
|
||||
<!-- <item name="drawableTint">@color/check_selection_color</item>-->
|
||||
<!-- Set color in the drawable instead of tint to allow multiple drawables-->
|
||||
<item name="drawableStartCompat">@drawable/ic_baseline_check_24_listview</item>
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<style name="BlackButton" parent="NiceButton">
|
||||
<item name="strokeColor">?attr/textColor</item>
|
||||
<item name="backgroundTint">?attr/iconGrayBackground</item>
|
||||
|
|
Loading…
Reference in a new issue