viewmodel dialogs

This commit is contained in:
LagradOst 2022-08-03 02:04:03 +02:00
parent 64ea5e2f4b
commit a99713fe0c
17 changed files with 655 additions and 192 deletions

View file

@ -1114,6 +1114,7 @@ data class NextAiring(
data class SeasonData( data class SeasonData(
val season: Int, val season: Int,
val name: String? = null, val name: String? = null,
val displaySeason : Int? = null, // will use season if null
) )
interface EpisodeResponse { interface EpisodeResponse {

View file

@ -332,6 +332,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
if (str.contains(appString)) { if (str.contains(appString)) {
for (api in OAuth2Apis) { for (api in OAuth2Apis) {
if (str.contains("/${api.redirectUrl}")) { if (str.contains("/${api.redirectUrl}")) {
val activity = this
ioSafe { ioSafe {
Log.i(TAG, "handleAppIntent $str") Log.i(TAG, "handleAppIntent $str")
val isSuccessful = api.handleRedirect(str) val isSuccessful = api.handleRedirect(str)
@ -342,10 +343,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
Log.i(TAG, "failed to authenticate ${api.name}") Log.i(TAG, "failed to authenticate ${api.name}")
} }
this.runOnUiThread { activity.runOnUiThread {
try { try {
showToast( showToast(
this, activity,
getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format( getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format(
api.name api.name
) )

View file

@ -54,7 +54,7 @@ class GogoanimeProvider : MainAPI() {
secretKeyString: String, secretKeyString: String,
encrypt: Boolean = true encrypt: Boolean = true
): String { ): 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 ivParameterSpec = IvParameterSpec(iv.toByteArray())
val secretKey = SecretKeySpec(secretKeyString.toByteArray(), "AES") val secretKey = SecretKeySpec(secretKeyString.toByteArray(), "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")

View file

@ -51,6 +51,32 @@ fun <T> LifecycleOwner.observeDirectly(liveData: LiveData<T>, action: (t: T) ->
action(currentValue) 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> { sealed class Resource<out T> {
data class Success<out T>(val value: T) : Resource<T>() data class Success<out T>(val value: T) : Resource<T>()
data class Failure( data class Failure(

View file

@ -125,6 +125,7 @@ class GeneratorPlayer : FullScreenPlayer() {
private fun loadExtractorJob(extractorLink: ExtractorLink?) { private fun loadExtractorJob(extractorLink: ExtractorLink?) {
currentVerifyLink?.cancel() currentVerifyLink?.cancel()
extractorLink?.let { extractorLink?.let {
currentVerifyLink = ioSafe { currentVerifyLink = ioSafe {
if (it.extractorData != null) { if (it.extractorData != null) {
@ -488,7 +489,9 @@ class GeneratorPlayer : FullScreenPlayer() {
.setView(R.layout.player_select_source_and_subs) .setView(R.layout.player_select_source_and_subs)
val sourceDialog = sourceBuilder.create() val sourceDialog = sourceBuilder.create()
selectSourceDialog = sourceDialog selectSourceDialog = sourceDialog
sourceDialog.show() sourceDialog.show()
val providerList = sourceDialog.sort_providers val providerList = sourceDialog.sort_providers
val subtitleList = sourceDialog.sort_subtitles val subtitleList = sourceDialog.sort_subtitles

View file

@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.ui.result package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.content.Intent.* import android.content.Intent.*
import android.content.res.ColorStateList import android.content.res.ColorStateList
@ -15,6 +16,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AbsListView import android.widget.AbsListView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone 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.CastButtonFactory
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.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick 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.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog 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
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute 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.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard 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_recommendations.*
import kotlinx.android.synthetic.main.result_sync.* import kotlinx.android.synthetic.main.result_sync.*
import kotlinx.android.synthetic.main.trailer_custom_layout.* import kotlinx.android.synthetic.main.trailer_custom_layout.*
import kotlinx.coroutines.runBlocking
const val START_ACTION_NORMAL = 0 const val START_ACTION_NORMAL = 0
const val START_ACTION_RESUME_LATEST = 1 const val START_ACTION_RESUME_LATEST = 1
@ -206,8 +209,6 @@ class ResultFragment : ResultTrailerPlayer() {
private var updateUIListener: (() -> Unit)? = null 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 viewModel: ResultViewModel2 //by activityViewModels()
private lateinit var syncModel: SyncViewModel private lateinit var syncModel: SyncViewModel
@ -418,7 +419,8 @@ class ResultFragment : ResultTrailerPlayer() {
viewModel.reloadEpisodes() viewModel.reloadEpisodes()
} }
var apiName: String = "" var loadingDialog: Dialog? = null
var popupDialog: Dialog? = null
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -466,7 +468,7 @@ class ResultFragment : ResultTrailerPlayer() {
// activity?.fixPaddingStatusbar(result_toolbar) // activity?.fixPaddingStatusbar(result_toolbar)
val url = arguments?.getString(URL_BUNDLE) 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 startAction = arguments?.getInt(START_ACTION_BUNDLE) ?: START_ACTION_NORMAL
startValue = arguments?.getInt(START_VALUE_BUNDLE) startValue = arguments?.getInt(START_VALUE_BUNDLE)
val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE) val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE)
@ -862,16 +864,16 @@ class ResultFragment : ResultTrailerPlayer() {
*/ */
observe(viewModel.episodes) { episodes -> observe(viewModel.episodes) { episodes ->
when (episodes) { when (episodes) {
is Resource.Failure -> { is ResourceSome.None -> {
result_episode_loading?.isVisible = false 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_episode_loading?.isVisible = true
// result_episodes?.isVisible = false result_episodes?.isVisible = false
} }
is Resource.Success -> { is ResourceSome.Success -> {
//result_episodes?.isVisible = true result_episodes?.isVisible = true
result_episode_loading?.isVisible = false result_episode_loading?.isVisible = false
(result_episodes?.adapter as? EpisodeAdapter?)?.updateList(episodes.value) (result_episodes?.adapter as? EpisodeAdapter?)?.updateList(episodes.value)
} }
@ -879,7 +881,7 @@ class ResultFragment : ResultTrailerPlayer() {
} }
observe(viewModel.selectedSeason) { text -> 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 the season button is visible the result season button will be next focus down
if (result_season_button?.isVisible == true) 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 -> observe(viewModel.selectedRange) { range ->
result_episode_select.setText(range) result_episode_select.setText(range)
@ -933,7 +999,8 @@ class ResultFragment : ResultTrailerPlayer() {
} }
observe(viewModel.rangeSelections) { range -> observe(viewModel.rangeSelections) { range ->
result_episode_select.setOnClickListener { view -> println("RANGE:$range")
result_episode_select?.setOnClickListener { view ->
view?.context?.let { ctx -> view?.context?.let { ctx ->
val names = range val names = range
.mapNotNull { (text, r) -> .mapNotNull { (text, r) ->
@ -987,6 +1054,33 @@ class ResultFragment : ResultTrailerPlayer() {
setRecommendations(recommendations, null) 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 -> observe(viewModel.page) { data ->
when (data) { when (data) {
is Resource.Success -> { is Resource.Success -> {
@ -1006,9 +1100,32 @@ class ResultFragment : ResultTrailerPlayer() {
result_cast_text.setText(d.actorsText) result_cast_text.setText(d.actorsText)
result_next_airing.setText(d.nextAiringEpisode) result_next_airing.setText(d.nextAiringEpisode)
result_next_airing_time.setText(d.nextAiringDate) result_next_airing_time.setText(d.nextAiringDate)
result_poster.setImage(d.posterImage) 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 result_cast_items?.isVisible = d.actors != null
@ -1016,6 +1133,7 @@ class ResultFragment : ResultTrailerPlayer() {
updateList(d.actors ?: emptyList()) updateList(d.actors ?: emptyList())
} }
result_open_in_browser?.isGone = d.url.isBlank()
result_open_in_browser?.setOnClickListener { result_open_in_browser?.setOnClickListener {
val i = Intent(ACTION_VIEW) val i = Intent(ACTION_VIEW)
i.data = Uri.parse(d.url) i.data = Uri.parse(d.url)
@ -1238,15 +1356,14 @@ class ResultFragment : ResultTrailerPlayer() {
Kitsu.isEnabled = Kitsu.isEnabled =
settingsManager.getBoolean(ctx.getString(R.string.show_kitsu_posters_key), true) settingsManager.getBoolean(ctx.getString(R.string.show_kitsu_posters_key), true)
val tempUrl = url if (url != null) {
if (tempUrl != null) {
result_reload_connectionerror.setOnClickListener { 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 { result_reload_connection_open_in_browser?.setOnClickListener {
val i = Intent(ACTION_VIEW) val i = Intent(ACTION_VIEW)
i.data = Uri.parse(tempUrl) i.data = Uri.parse(url)
try { try {
startActivity(i) startActivity(i)
} catch (e: Exception) { } catch (e: Exception) {
@ -1256,7 +1373,7 @@ class ResultFragment : ResultTrailerPlayer() {
result_open_in_browser?.setOnClickListener { result_open_in_browser?.setOnClickListener {
val i = Intent(ACTION_VIEW) val i = Intent(ACTION_VIEW)
i.data = Uri.parse(tempUrl) i.data = Uri.parse(url)
try { try {
startActivity(i) startActivity(i)
} catch (e: Exception) { } catch (e: Exception) {
@ -1267,7 +1384,7 @@ class ResultFragment : ResultTrailerPlayer() {
// bloats the navigation on tv // bloats the navigation on tv
if (context?.isTrueTvSettings() == false) { if (context?.isTrueTvSettings() == false) {
result_meta_site?.setOnClickListener { result_meta_site?.setOnClickListener {
it.context?.openBrowser(tempUrl) it.context?.openBrowser(url)
} }
result_meta_site?.isFocusable = true result_meta_site?.isFocusable = true
} else { } else {
@ -1276,7 +1393,7 @@ class ResultFragment : ResultTrailerPlayer() {
if (restart || !viewModel.hasLoaded()) { if (restart || !viewModel.hasLoaded()) {
//viewModel.clear() //viewModel.clear()
viewModel.load(tempUrl, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX viewModel.load(url, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX
} }
} }
} }

View file

@ -36,17 +36,16 @@ import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
import com.lagradost.cloudstream3.utils.CastHelper.startCast import com.lagradost.cloudstream3.utils.CastHelper.startCast
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.DataStore.setKey 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.checkWrite
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.requestRW import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -86,7 +85,6 @@ data class ResultData(
val yearText: UiText?, val yearText: UiText?,
val nextAiringDate: UiText?, val nextAiringDate: UiText?,
val nextAiringEpisode: UiText?, val nextAiringEpisode: UiText?,
val playMovieText: UiText?,
val plotHeaderText: UiText, val plotHeaderText: UiText,
) )
@ -118,7 +116,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
val hours: Long = TimeUnit.SECONDS.toHours(seconds) - days * 24 val hours: Long = TimeUnit.SECONDS.toHours(seconds) - days * 24
val minute = val minute =
TimeUnit.SECONDS.toMinutes(seconds) - TimeUnit.SECONDS.toHours(seconds) * 60 TimeUnit.SECONDS.toMinutes(seconds) - TimeUnit.SECONDS.toHours(seconds) * 60
nextAiringEpisode = when { nextAiringDate = when {
days > 0 -> { days > 0 -> {
txt( txt(
R.string.next_episode_time_day_format, R.string.next_episode_time_day_format,
@ -138,11 +136,11 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
) )
else -> null else -> null
}?.also { }?.also {
nextAiringDate = txt(R.string.next_episode_format, airing.episode) nextAiringEpisode = txt(R.string.next_episode_format, airing.episode)
} }
} }
} }
val dur = duration
return ResultData( return ResultData(
syncData = syncData, syncData = syncData,
plotHeaderText = txt( plotHeaderText = txt(
@ -151,14 +149,6 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
else -> R.string.result_plot 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, nextAiringDate = nextAiringDate,
nextAiringEpisode = nextAiringEpisode, nextAiringEpisode = nextAiringEpisode,
posterImage = img( posterImage = img(
@ -192,7 +182,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
TvType.Live -> R.string.live_singular TvType.Live -> R.string.live_singular
} }
), ),
yearText = txt(year), yearText = txt(year?.toString()),
apiName = txt(apiName), apiName = txt(apiName),
ratingText = rating?.div(1000f)?.let { txt(R.string.rating_format, it) }, ratingText = rating?.div(1000f)?.let { txt(R.string.rating_format, it) },
vpnText = txt( vpnText = txt(
@ -204,7 +194,10 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
), ),
metaText = metaText =
if (repo.providerType == ProviderType.MetaProvider) txt(R.string.provider_info_meta) else null, 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) { onGoingText = if (this is EpisodeResponse) {
txt( txt(
when (showStatus) { when (showStatus) {
@ -245,21 +238,43 @@ sealed class SelectPopup {
val map: Int?, val map: Int?,
val callback: (Int?) -> Unit val callback: (Int?) -> Unit
) : SelectPopup() ) : SelectPopup()
}
fun SelectPopup.transformResult(context: Context, input: Int?): Int? { fun SelectPopup.callback(context: Context, input: Int?) {
if (input == null) return null val ret = transformResult(context, input)
return when (this) { return when (this) {
is SelectArray -> context.resources.getIntArray(map ?: return input).getOrNull(input) is SelectPopup.SelectArray -> callback(ret)
?: input is SelectPopup.SelectText -> callback(ret)
is SelectText -> input
}
} }
}
fun SelectPopup.getOptions(context: Context): List<String> { fun SelectPopup.transformResult(context: Context, input: Int?): Int? {
return when (this) { if (input == null) return null
is SelectArray -> context.resources.getStringArray(options).toList() return when (this) {
is SelectText -> options.map { it.asString(context) } 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>>> */ /** map<dub, map<season, List<episode>>> */
private var currentEpisodes: Map<EpisodeIndexer, List<ResultEpisode>> = mapOf() private var currentEpisodes: Map<EpisodeIndexer, List<ResultEpisode>> = mapOf()
private var currentRanges: Map<EpisodeIndexer, List<EpisodeRange>> = 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 currentMeta: SyncAPI.SyncResult? = null
private var currentSync: Map<String, String>? = null private var currentSync: Map<String, String>? = null
private var currentIndex: EpisodeIndexer? = null private var currentIndex: EpisodeIndexer? = null
@ -294,13 +311,17 @@ class ResultViewModel2 : ViewModel() {
MutableLiveData(Resource.Loading()) MutableLiveData(Resource.Loading())
val page: LiveData<Resource<ResultData>> = _page val page: LiveData<Resource<ResultData>> = _page
private val _episodes: MutableLiveData<Resource<List<ResultEpisode>>> = private val _episodes: MutableLiveData<ResourceSome<List<ResultEpisode>>> =
MutableLiveData(Resource.Loading()) MutableLiveData(ResourceSome.Loading())
val episodes: LiveData<Resource<List<ResultEpisode>>> = _episodes val episodes: LiveData<ResourceSome<List<ResultEpisode>>> = _episodes
private val _episodesCountText: MutableLiveData<UiText?> = private val _movie: MutableLiveData<ResourceSome<Pair<UiText, ResultEpisode>>> =
MutableLiveData(null) MutableLiveData(ResourceSome.None)
val episodesCountText: LiveData<UiText?> = _episodesCountText 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()) private val _trailers: MutableLiveData<List<TrailerData>> = MutableLiveData(mutableListOf())
val trailers: LiveData<List<TrailerData>> = _trailers val trailers: LiveData<List<TrailerData>> = _trailers
@ -318,24 +339,23 @@ class ResultViewModel2 : ViewModel() {
MutableLiveData(emptyList()) MutableLiveData(emptyList())
val seasonSelections: LiveData<List<Pair<UiText?, Int>>> = _seasonSelections val seasonSelections: LiveData<List<Pair<UiText?, Int>>> = _seasonSelections
private val _recommendations: MutableLiveData<List<SearchResponse>> = private val _recommendations: MutableLiveData<List<SearchResponse>> =
MutableLiveData(emptyList()) MutableLiveData(emptyList())
val recommendations: LiveData<List<SearchResponse>> = _recommendations val recommendations: LiveData<List<SearchResponse>> = _recommendations
private val _selectedRange: MutableLiveData<UiText?> = private val _selectedRange: MutableLiveData<Some<UiText>> =
MutableLiveData(null) MutableLiveData(Some.None)
val selectedRange: LiveData<UiText?> = _selectedRange val selectedRange: LiveData<Some<UiText>> = _selectedRange
private val _selectedSeason: MutableLiveData<UiText?> = private val _selectedSeason: MutableLiveData<Some<UiText>> =
MutableLiveData(null) MutableLiveData(Some.None)
val selectedSeason: LiveData<UiText?> = _selectedSeason val selectedSeason: LiveData<Some<UiText>> = _selectedSeason
private val _selectedDubStatus: MutableLiveData<UiText?> = MutableLiveData(null) private val _selectedDubStatus: MutableLiveData<Some<UiText>> = MutableLiveData(Some.None)
val selectedDubStatus: LiveData<UiText?> = _selectedDubStatus val selectedDubStatus: LiveData<Some<UiText>> = _selectedDubStatus
private val _loadedLinks: MutableLiveData<LinkProgress?> = MutableLiveData(null) private val _loadedLinks: MutableLiveData<Some<LinkProgress>> = MutableLiveData(Some.None)
val loadedLinks: LiveData<LinkProgress?> = _loadedLinks val loadedLinks: LiveData<Some<LinkProgress>> = _loadedLinks
companion object { companion object {
const val TAG = "RVM2" const val TAG = "RVM2"
@ -680,8 +700,8 @@ class ResultViewModel2 : ViewModel() {
private val _watchStatus: MutableLiveData<WatchType> = MutableLiveData(WatchType.NONE) private val _watchStatus: MutableLiveData<WatchType> = MutableLiveData(WatchType.NONE)
val watchStatus: LiveData<WatchType> get() = _watchStatus val watchStatus: LiveData<WatchType> get() = _watchStatus
private val _selectPopup: MutableLiveData<SelectPopup?> = MutableLiveData(null) private val _selectPopup: MutableLiveData<Some<SelectPopup>> = MutableLiveData(Some.None)
val selectPopup: LiveData<SelectPopup?> get() = _selectPopup val selectPopup: LiveData<Some<SelectPopup>> get() = _selectPopup
fun updateWatchStatus(status: WatchType) { fun updateWatchStatus(status: WatchType) {
val currentId = currentId ?: return val currentId = currentId ?: return
@ -707,14 +727,15 @@ class ResultViewModel2 : ViewModel() {
) )
} }
private suspend fun startChromecast( private fun startChromecast(
activity: Activity?, activity: Activity?,
result: ResultEpisode, result: ResultEpisode,
isVisible: Boolean = true isVisible: Boolean = true
) { ) {
if (activity == null) return if (activity == null) return
val data = loadLinks(result, isVisible = isVisible, isCasting = true) loadLinks(result, isVisible = isVisible, isCasting = true) { data ->
startChromecast(activity, result, data.links, data.subs, 0) startChromecast(activity, result, data.links, data.subs, 0)
}
} }
private fun startChromecast( private fun startChromecast(
@ -742,51 +763,100 @@ class ResultViewModel2 : ViewModel() {
) )
} }
private val popupCallback: ((Int) -> Unit)? = null
fun cancelLinks() { fun cancelLinks() {
println("called::cancelLinks")
currentLoadLinkJob?.cancel() 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 var currentLoadLinkJob: Job? = null
private suspend fun acquireSingleLink( private fun acquireSingleLink(
result: ResultEpisode, result: ResultEpisode,
isCasting: Boolean, isCasting: Boolean,
text: UiText, text: UiText,
callback: (Pair<LinkLoadingResult, Int>) -> Unit, callback: (Pair<LinkLoadingResult, Int>) -> Unit,
) { ) {
currentLoadLinkJob = viewModelScope.launch { loadLinks(result, isVisible = true, isCasting = isCasting) { links ->
val links = loadLinks(result, isVisible = true, isCasting = isCasting) postPopup(
text,
_selectPopup.postValue( links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) {
SelectPopup.SelectText( callback.invoke(links to (it ?: return@postPopup))
text, }
links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) {
callback.invoke(links to (it ?: return@SelectText))
})
} }
} }
private suspend fun acquireSingleSubtitle( private fun acquireSingleSubtitle(
result: ResultEpisode, result: ResultEpisode,
isCasting: Boolean, isCasting: Boolean,
text: UiText, text: UiText,
callback: (Pair<LinkLoadingResult, Int>) -> Unit, callback: (Pair<LinkLoadingResult, Int>) -> Unit,
) { ) {
currentLoadLinkJob = viewModelScope.launch { loadLinks(result, isVisible = true, isCasting = isCasting) { links ->
val links = loadLinks(result, isVisible = true, isCasting = isCasting) postPopup(
text,
_selectPopup.postValue( links.subs.map { txt(it.name) })
SelectPopup.SelectText( {
text, callback.invoke(links to (it ?: return@postPopup))
links.subs.map { txt(it.name) }) { }
callback.invoke(links to (it ?: return@SelectText))
})
} }
} }
suspend fun loadLinks( suspend fun CoroutineScope.loadLinks(
result: ResultEpisode, result: ResultEpisode,
isVisible: Boolean, isVisible: Boolean,
isCasting: Boolean, isCasting: Boolean,
@ -797,11 +867,12 @@ class ResultViewModel2 : ViewModel() {
val links: MutableSet<ExtractorLink> = mutableSetOf() val links: MutableSet<ExtractorLink> = mutableSetOf()
val subs: MutableSet<SubtitleData> = mutableSetOf() val subs: MutableSet<SubtitleData> = mutableSetOf()
fun updatePage() { fun updatePage() {
if (isVisible) { if (isVisible && isActive) {
_loadedLinks.postValue(LinkProgress(links.size, subs.size)) _loadedLinks.postValue(some(LinkProgress(links.size, subs.size)))
} }
} }
try { try {
updatePage()
tempGenerator.generateLinks(clearCache, isCasting, { (link, _) -> tempGenerator.generateLinks(clearCache, isCasting, { (link, _) ->
if (link != null) { if (link != null) {
links += link links += link
@ -814,7 +885,7 @@ class ResultViewModel2 : ViewModel() {
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} finally { } finally {
_loadedLinks.postValue(null) _loadedLinks.postValue(Some.None)
} }
return LinkLoadingResult(sortUrls(links), sortSubs(subs)) return LinkLoadingResult(sortUrls(links), sortSubs(subs))
@ -884,20 +955,22 @@ class ResultViewModel2 : ViewModel() {
private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) { private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) {
when (click.action) { when (click.action) {
ACTION_SHOW_OPTIONS -> { ACTION_SHOW_OPTIONS -> {
_selectPopup.postValue( postPopup(
SelectPopup.SelectArray( txt(
txt(""), // TODO FIX activity?.getNameFull(
R.array.episode_long_click_options, click.data.name,
R.array.episode_long_click_options_values click.data.episode,
) { result -> click.data.season
if (result == null) return@SelectArray ) ?: ""
viewModelScope.launch { ), // TODO FIX
handleEpisodeClickEvent( R.array.episode_long_click_options,
activity, R.array.episode_long_click_options_values
click.copy(action = result) ) { result ->
) handleEpisodeClickEvent(
} activity,
}) click.copy(action = result ?: return@postPopup)
)
}
} }
ACTION_CLICK_DEFAULT -> { ACTION_CLICK_DEFAULT -> {
activity?.let { ctx -> activity?.let { ctx ->
@ -986,7 +1059,14 @@ class ResultViewModel2 : ViewModel() {
} }
} }
ACTION_RELOAD_EPISODE -> { 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 -> { ACTION_CHROME_CAST_MIRROR -> {
acquireSingleLink( acquireSingleLink(
@ -1030,8 +1110,12 @@ class ResultViewModel2 : ViewModel() {
startChromecast(activity, click.data) startChromecast(activity, click.data)
} }
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> { ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> {
currentLoadLinkJob = viewModelScope.launch { loadLinks(click.data, isVisible = true, isCasting = true) { links ->
playWithVlc(activity, loadLinks(click.data, true, true), click.data.id) playWithVlc(
activity,
links,
click.data.id
)
} }
} }
ACTION_PLAY_EPISODE_IN_PLAYER -> { ACTION_PLAY_EPISODE_IN_PLAYER -> {
@ -1176,6 +1260,13 @@ class ResultViewModel2 : ViewModel() {
postEpisodeRange(currentIndex?.copy(season = season), currentRange) 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> { private fun getEpisodes(indexer: EpisodeIndexer, range: EpisodeRange): List<ResultEpisode> {
val startIndex = range.startIndex val startIndex = range.startIndex
val length = range.length val length = range.length
@ -1192,15 +1283,50 @@ class ResultViewModel2 : ViewModel() {
?: emptyList() ?: 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() { fun reloadEpisodes() {
_episodes.postValue( if (currentResponse?.isMovie() == true) {
Resource.Success( postMovie()
getEpisodes( } else {
currentIndex ?: return, _episodes.postValue(
currentRange ?: return ResourceSome.Success(
getEpisodes(
currentIndex ?: return,
currentRange ?: return
)
) )
) )
) _movie.postValue(ResourceSome.None)
}
} }
private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) { private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) {
@ -1208,45 +1334,79 @@ class ResultViewModel2 : ViewModel() {
return return
} }
val size = currentEpisodes[indexer]?.size val episodes = currentEpisodes[indexer]
val ranges = currentRanges[indexer]
_episodesCountText.postValue( val size = episodes?.size
txt( val isMovie = currentResponse?.isMovie() == true
R.string.episode_format,
txt(if (size == 1) R.string.episode else R.string.episodes),
size
)
)
currentIndex = indexer currentIndex = indexer
currentRange = range 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( _selectedSeason.postValue(
when (indexer.season) { some(
0 -> txt(R.string.no_season) if (isMovie || currentSeasons.size <= 1) null else
else -> txt(R.string.season_format, R.string.season, indexer.season) //TODO FIX 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( _selectedRange.postValue(
if ((currentRanges[indexer]?.size ?: 0) > 1) { some(
txt(R.string.episodes_range, range.startEpisode, range.endEpisode) if (isMovie) null else if ((currentRanges[indexer]?.size ?: 0) > 1) {
} else { txt(R.string.episodes_range, range.startEpisode, range.endEpisode)
null } else {
} null
}
)
)
_selectedDubStatus.postValue(
some(
if (isMovie || currentDubStatus.size <= 1) null else
txt(indexer.dubStatus)
)
) )
_selectedDubStatus.postValue(txt(indexer.dubStatus))
//TODO SET KEYS //TODO SET KEYS
preferStartEpisode = range.startEpisode preferStartEpisode = range.startEpisode
preferStartSeason = indexer.season preferStartSeason = indexer.season
preferDubStatus = indexer.dubStatus preferDubStatus = indexer.dubStatus
generator = currentEpisodes[indexer]?.let { list -> generator = if (isMovie) {
RepoLinkGenerator(list) getMovie()?.let { RepoLinkGenerator(listOf(it)) }
} else {
episodes?.let { list ->
RepoLinkGenerator(list)
}
} }
val ret = getEpisodes(indexer, range) if (isMovie) {
_episodes.postValue(Resource.Success(ret)) postMovie()
} else {
val ret = getEpisodes(indexer, range)
_episodes.postValue(ResourceSome.Success(ret))
}
} }
private suspend fun postSuccessful( private suspend fun postSuccessful(
@ -1262,11 +1422,13 @@ class ResultViewModel2 : ViewModel() {
} }
private suspend fun postEpisodes(loadResponse: LoadResponse, updateFillers: Boolean) { private suspend fun postEpisodes(loadResponse: LoadResponse, updateFillers: Boolean) {
_episodes.postValue(Resource.Loading()) _episodes.postValue(ResourceSome.Loading())
val mainId = loadResponse.getId() val mainId = loadResponse.getId()
currentId = mainId currentId = mainId
_watchStatus.postValue(getResultWatchState(mainId))
if (updateFillers && loadResponse is AnimeLoadResponse) { if (updateFillers && loadResponse is AnimeLoadResponse) {
updateFillers(loadResponse.name) 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 currentEpisodes = allEpisodes
val ranges = getRanges(allEpisodes) val ranges = getRanges(allEpisodes)
currentRanges = ranges currentRanges = ranges
// this takes the indexer most preferable by the user given the current sorting // this takes the indexer most preferable by the user given the current sorting
val min = ranges.keys.minByOrNull { index -> val min = ranges.keys.minByOrNull { index ->
kotlin.math.abs( kotlin.math.abs(
@ -1464,7 +1646,7 @@ class ResultViewModel2 : ViewModel() {
) = ) =
viewModelScope.launch { viewModelScope.launch {
_page.postValue(Resource.Loading(url)) _page.postValue(Resource.Loading(url))
_episodes.postValue(Resource.Loading(url)) _episodes.postValue(ResourceSome.Loading())
preferDubStatus = dubStatus preferDubStatus = dubStatus
currentShowFillers = showFillers currentShowFillers = showFillers

View file

@ -8,6 +8,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
@ -152,3 +153,11 @@ fun TextView?.setTextHtml(text: UiText?) {
this.text = str.html() 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)
}

View file

@ -179,21 +179,21 @@ object AppUtils {
@WorkerThread @WorkerThread
fun Context.addProgramsToContinueWatching(data: List<DataStoreHelper.ResumeWatchingResult>) { fun Context.addProgramsToContinueWatching(data: List<DataStoreHelper.ResumeWatchingResult>) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val context = this
ioSafe { ioSafe {
data.forEach { episodeInfo -> data.forEach { episodeInfo ->
try { try {
val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, this) val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, context)
val nextProgram = buildWatchNextProgramUri(this, episodeInfo) val nextProgram = buildWatchNextProgramUri(context, episodeInfo)
// If the program is already in the Watch Next row, update it // If the program is already in the Watch Next row, update it
if (program != null && id != null) { if (program != null && id != null) {
PreviewChannelHelper(this).updateWatchNextProgram( PreviewChannelHelper(context).updateWatchNextProgram(
nextProgram, nextProgram,
id, id,
) )
} else { } else {
PreviewChannelHelper(this) PreviewChannelHelper(context)
.publishWatchNextProgram(nextProgram) .publishWatchNextProgram(nextProgram)
} }
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -12,7 +12,7 @@ object Coroutines {
} }
} }
fun ioSafe(work: suspend (() -> Unit)): Job { fun ioSafe(work: suspend (CoroutineScope.() -> Unit)): Job {
return CoroutineScope(Dispatchers.IO).launch { return CoroutineScope(Dispatchers.IO).launch {
try { try {
work() 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) { return withContext(Dispatchers.IO) {
work() work()
} }

View file

@ -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.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage 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 { object SingleSelectionHelper {
fun Activity?.showOptionSelectStringRes( fun Activity?.showOptionSelectStringRes(
@ -21,7 +24,7 @@ object SingleSelectionHelper {
tvOptions: List<Int> = listOf(), tvOptions: List<Int> = listOf(),
callback: (Pair<Boolean, Int>) -> Unit callback: (Pair<Boolean, Int>) -> Unit
) { ) {
if(this == null) return if (this == null) return
this.showOptionSelect( this.showOptionSelect(
view, view,
@ -39,7 +42,7 @@ object SingleSelectionHelper {
tvOptions: List<String>, tvOptions: List<String>,
callback: (Pair<Boolean, Int>) -> Unit callback: (Pair<Boolean, Int>) -> Unit
) { ) {
if(this == null) return if (this == null) return
if (this.isTvSettings()) { if (this.isTvSettings()) {
val builder = val builder =
@ -86,42 +89,44 @@ object SingleSelectionHelper {
showApply: Boolean, showApply: Boolean,
isMultiSelect: Boolean, isMultiSelect: Boolean,
callback: (List<Int>) -> Unit, 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 realShowApply = showApply || isMultiSelect
val listView = dialog.findViewById<ListView>(R.id.listview1)!! val listView = dialog.listview1//.findViewById<ListView>(R.id.listview1)!!
val textView = dialog.findViewById<TextView>(R.id.text1)!! val textView = dialog.text1//.findViewById<TextView>(R.id.text1)!!
val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!! val applyButton = dialog.apply_btt//.findViewById<TextView>(R.id.apply_btt)
val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!! val cancelButton = dialog.cancel_btt//findViewById<TextView>(R.id.cancel_btt)
val applyHolder = dialog.findViewById<LinearLayout>(R.id.apply_btt_holder)!! val applyHolder = dialog.apply_btt_holder//.findViewById<LinearLayout>(R.id.apply_btt_holder)
applyHolder.isVisible = realShowApply applyHolder?.isVisible = realShowApply
if (!realShowApply) { if (!realShowApply) {
val params = listView.layoutParams as LinearLayout.LayoutParams val params = listView.layoutParams as LinearLayout.LayoutParams
params.setMargins(listView.marginLeft, listView.marginTop, listView.marginRight, 0) params.setMargins(listView.marginLeft, listView.marginTop, listView.marginRight, 0)
listView.layoutParams = params 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) arrayAdapter.addAll(items)
listView.adapter = arrayAdapter listView?.adapter = arrayAdapter
if (isMultiSelect) { if (isMultiSelect) {
listView.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
} else { } else {
listView.choiceMode = AbsListView.CHOICE_MODE_SINGLE listView?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
} }
for (select in selectedIndex) { for (select in selectedIndex) {
listView.setItemChecked(select, true) listView?.setItemChecked(select, true)
} }
selectedIndex.minOrNull()?.let { selectedIndex.minOrNull()?.let {
listView.setSelection(it) listView?.setSelection(it)
} }
// var lastSelectedIndex = if(selectedIndex.isNotEmpty()) selectedIndex.first() else -1 // var lastSelectedIndex = if(selectedIndex.isNotEmpty()) selectedIndex.first() else -1
@ -130,7 +135,7 @@ object SingleSelectionHelper {
dismissCallback.invoke() dismissCallback.invoke()
} }
listView.setOnItemClickListener { _, _, which, _ -> listView?.setOnItemClickListener { _, _, which, _ ->
// lastSelectedIndex = which // lastSelectedIndex = which
if (realShowApply) { if (realShowApply) {
if (!isMultiSelect) { if (!isMultiSelect) {
@ -142,7 +147,7 @@ object SingleSelectionHelper {
} }
} }
if (realShowApply) { if (realShowApply) {
applyButton.setOnClickListener { applyButton?.setOnClickListener {
val list = ArrayList<Int>() val list = ArrayList<Int>()
for (index in 0 until listView.count) { for (index in 0 until listView.count) {
if (listView.checkedItemPositions[index]) if (listView.checkedItemPositions[index])
@ -151,7 +156,7 @@ object SingleSelectionHelper {
callback.invoke(list) callback.invoke(list)
dialog.dismissSafe(this) dialog.dismissSafe(this)
} }
cancelButton.setOnClickListener { cancelButton?.setOnClickListener {
dialog.dismissSafe(this) dialog.dismissSafe(this)
} }
} }
@ -166,7 +171,7 @@ object SingleSelectionHelper {
callback: (String) -> Unit, callback: (String) -> Unit,
dismissCallback: () -> Unit dismissCallback: () -> Unit
) { ) {
if(this == null) return if (this == null) return
val inputView = dialog.findViewById<EditText>(R.id.nginx_text_input)!! val inputView = dialog.findViewById<EditText>(R.id.nginx_text_input)!!
val textView = dialog.findViewById<TextView>(R.id.text1)!! val textView = dialog.findViewById<TextView>(R.id.text1)!!
@ -205,7 +210,7 @@ object SingleSelectionHelper {
dismissCallback: () -> Unit, dismissCallback: () -> Unit,
callback: (List<Int>) -> Unit, callback: (List<Int>) -> Unit,
) { ) {
if(this == null) return if (this == null) return
val builder = val builder =
AlertDialog.Builder(this, R.style.AlertDialogCustom) AlertDialog.Builder(this, R.style.AlertDialogCustom)
@ -224,7 +229,7 @@ object SingleSelectionHelper {
dismissCallback: () -> Unit, dismissCallback: () -> Unit,
callback: (Int) -> Unit, callback: (Int) -> Unit,
) { ) {
if(this == null) return if (this == null) return
val builder = val builder =
AlertDialog.Builder(this, R.style.AlertDialogCustom) 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( fun Activity.showNginxTextInputDialog(
name: String, name: String,
value: String, value: String,

View 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>

View 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>

View file

@ -843,7 +843,6 @@
<LinearLayout <LinearLayout
android:id="@+id/result_next_airing_holder" android:id="@+id/result_next_airing_holder"
android:layout_gravity="start" android:layout_gravity="start"
android:paddingBottom="15dp"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content">

View file

@ -12,6 +12,6 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:src="@drawable/default_cover" android:src="@drawable/default_cover"
android:background="#fffff0" android:background="?attr/primaryGrayBackground"
android:contentDescription="@string/poster_image" /> android:contentDescription="@string/poster_image" />
</LinearLayout> </LinearLayout>

View file

@ -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" />

View file

@ -448,7 +448,15 @@
<item name="android:textColor">?attr/textColor</item> <item name="android:textColor">?attr/textColor</item>
</style> </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_width">match_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item> <item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item>
@ -458,15 +466,13 @@
<item name="android:gravity">center_vertical</item> <item name="android:gravity">center_vertical</item>
<item name="android:paddingStart">12dp</item> <item name="android:paddingStart">12dp</item>
<item name="android:paddingEnd">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:ellipsize">marquee</item>
<item name="android:foreground">?attr/selectableItemBackgroundBorderless</item> <item name="android:foreground">?attr/selectableItemBackgroundBorderless</item>
<item name="android:drawablePadding">20dp</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>
<style name="BlackButton" parent="NiceButton"> <style name="BlackButton" parent="NiceButton">
<item name="strokeColor">?attr/textColor</item> <item name="strokeColor">?attr/textColor</item>
<item name="backgroundTint">?attr/iconGrayBackground</item> <item name="backgroundTint">?attr/iconGrayBackground</item>