not done 2

This commit is contained in:
LagradOst 2022-08-01 04:46:43 +02:00
parent 3f19429805
commit f57b12d89c
5 changed files with 312 additions and 267 deletions

View file

@ -21,7 +21,6 @@ import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.view.isGone import androidx.core.view.isGone
@ -39,7 +38,6 @@ 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.getId import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.getCastSession
@ -84,7 +82,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.requestRW import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFileName import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFileName
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result.*
@ -95,10 +92,8 @@ 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.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit
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
@ -458,11 +453,6 @@ class ResultFragment : ResultTrailerPlayer() {
0 // THIS IS USED TO PREVENT LATE EVENTS, AFTER DISMISS WAS CLICKED 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
private var currentHeaderName: String? = null
private var currentType: TvType? = null
private var currentEpisodes: List<ResultEpisode>? = null
private var downloadButton: EasyDownloadButton? = null
private var syncdata: Map<String, String>? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -490,13 +480,6 @@ class ResultFragment : ResultTrailerPlayer() {
super.onDestroyView() super.onDestroyView()
} }
override fun onDestroy() {
//requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR
super.onDestroy()
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
activity?.let { activity?.let {
@ -563,52 +546,6 @@ class ResultFragment : ResultTrailerPlayer() {
var startAction: Int? = null var startAction: Int? = null
private var startValue: Int? = null private var startValue: Int? = null
private fun setFormatText(textView: TextView?, @StringRes format: Int, arg: Any?) {
// java.util.IllegalFormatConversionException: f != java.lang.Integer
// This can fail with malformed formatting
normalSafeApiCall {
if (arg == null) {
textView?.isVisible = false
} else {
val text = context?.getString(format)?.format(arg)
if (text == null) {
textView?.isVisible = false
} else {
textView?.isVisible = true
textView?.text = text
}
}
}
}
private fun setDuration(duration: Int?) {
setFormatText(result_meta_duration, R.string.duration_format, duration)
}
private fun setShow(showStatus: ShowStatus?) {
val status = when (showStatus) {
null -> null
ShowStatus.Ongoing -> R.string.status_ongoing
ShowStatus.Completed -> R.string.status_completed
}
if (status == null) {
result_meta_status?.isVisible = false
} else {
context?.getString(status)?.let {
result_meta_status?.text = it
}
}
}
private fun setYear(year: Int?) {
setFormatText(result_meta_year, R.string.year_format, year)
}
private fun setRating(rating: Int?) {
setFormatText(result_meta_rating, R.string.rating_format, rating?.div(1000f))
}
var currentTrailers: List<ExtractorLink> = emptyList() var currentTrailers: List<ExtractorLink> = emptyList()
var currentTrailerIndex = 0 var currentTrailerIndex = 0
@ -1339,54 +1276,7 @@ class ResultFragment : ResultTrailerPlayer() {
result_episode_select?.isFocusableInTouchMode = context?.isTvSettings() == true result_episode_select?.isFocusableInTouchMode = context?.isTvSettings() == true
result_dub_select?.isFocusableInTouchMode = context?.isTvSettings() == true result_dub_select?.isFocusableInTouchMode = context?.isTvSettings() == true
observe(viewModel.selectedSeason) { season ->
result_season_button?.text = fromIndexToSeasonText(season)
}
observe(viewModel.seasonSelections) { seasonList ->
result_season_button?.visibility = if (seasonList.size <= 1) GONE else VISIBLE.also {
// If the season button is visible the result season button will be next focus down
if (result_series_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_season_button)
else
setFocusUpAndDown(result_bookmark_button, result_season_button)
}
result_season_button?.setOnClickListener {
result_season_button?.popupMenuNoIconsAndNoStringRes(
items = seasonList
.map { (name, season) ->
Pair(
season ?: -2,
name ?: fromIndexToSeasonText(season)
)
},
) {
val id = this.itemId
viewModel.changeSeason(if (id == -2) null else id)
}
}
}
observe(viewModel.selectedRange) { range ->
result_episode_select?.text = range
}
observe(viewModel.rangeOptions) { range ->
episodeRanges = range
result_episode_select?.visibility = if (range.size <= 1) GONE else VISIBLE.also {
// If Season button is invisible then the bookmark button next focus is episode select
if (result_season_button?.isVisible != true) {
if (result_series_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_episode_select)
else
setFocusUpAndDown(result_bookmark_button, result_episode_select)
}
}
}
context?.let { ctx -> context?.let { ctx ->
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice) val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
@ -1546,6 +1436,7 @@ class ResultFragment : ResultTrailerPlayer() {
result_overlapping_panels?.setStartPanelLockState(if (closed) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED) result_overlapping_panels?.setStartPanelLockState(if (closed) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
} }
/*
observe(viewModel.episodes) { episodeList -> observe(viewModel.episodes) { episodeList ->
lateFixDownloadButton(episodeList.size <= 1) // movies can have multible parts but still be *movies* this will fix this lateFixDownloadButton(episodeList.size <= 1) // movies can have multible parts but still be *movies* this will fix this
var isSeriesVisible = false var isSeriesVisible = false
@ -1666,7 +1557,7 @@ class ResultFragment : ResultTrailerPlayer() {
startAction = null startAction = null
startValue = null startValue = null
} }
*/
observe(viewModel.episodes) { episodes -> observe(viewModel.episodes) { episodes ->
when (episodes) { when (episodes) {
is Resource.Failure -> { is Resource.Failure -> {
@ -1689,20 +1580,21 @@ class ResultFragment : ResultTrailerPlayer() {
} }
} }
observe(viewModel.dubStatus) { status -> observe(viewModel.selectedSeason) { text ->
result_dub_select?.apply { result_season_button?.setText(text)
isVisible = status != null
status?.toString()?.let { // If the season button is visible the result season button will be next focus down
text = it if (result_season_button?.isVisible == true)
} if (result_series_parent?.isVisible == true)
} setFocusUpAndDown(result_resume_series_button, result_season_button)
else
setFocusUpAndDown(result_bookmark_button, result_season_button)
} }
// val preferDub = context?.getApiDubstatusSettings()?.all { it == DubStatus.Dubbed } == true observe(viewModel.selectedDubStatus) { status ->
result_dub_select?.setText(status)
observe(viewModel.dubSubSelections) { range ->
result_dub_select?.visibility = if (range.size <= 1) GONE else VISIBLE
if (result_dub_select?.isVisible == true)
if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) { if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) {
if (result_series_parent?.isVisible == true) if (result_series_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_dub_select) setFocusUpAndDown(result_resume_series_button, result_dub_select)
@ -1711,53 +1603,90 @@ class ResultFragment : ResultTrailerPlayer() {
} }
} }
result_cast_items?.setOnFocusChangeListener { _, hasFocus -> observe(viewModel.selectedRange) { range ->
// Always escape focus result_episode_select.setText(range)
if (hasFocus) result_bookmark_button?.requestFocus()
// If Season button is invisible then the bookmark button next focus is episode select
if (result_episode_select?.isVisible == true)
if (result_season_button?.isVisible != true) {
if (result_series_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_episode_select)
else
setFocusUpAndDown(result_bookmark_button, result_episode_select)
}
} }
result_dub_select.setOnClickListener { // val preferDub = context?.getApiDubstatusSettings()?.all { it == DubStatus.Dubbed } == true
val ranges = dubRange
if (ranges != null) { observe(viewModel.dubSubSelections) { range ->
it.popupMenuNoIconsAndNoStringRes(ranges result_dub_select.setOnClickListener { view ->
.map { status -> view?.context?.let { ctx ->
view.popupMenuNoIconsAndNoStringRes(range
.mapNotNull { (text, status) ->
Pair( Pair(
status.ordinal, status.ordinal,
status.toString() text?.asStringNull(ctx) ?: return@mapNotNull null
) )
} }) {
.toList()) {
viewModel.changeDubStatus(DubStatus.values()[itemId]) viewModel.changeDubStatus(DubStatus.values()[itemId])
} }
} }
} }
}
result_episode_select?.setOnClickListener { observe(viewModel.rangeSelections) { range ->
val ranges = episodeRanges result_episode_select.setOnClickListener { view ->
if (ranges != null) { view?.context?.let { ctx ->
it.popupMenuNoIconsAndNoStringRes(ranges.mapIndexed { index, s -> Pair(index, s) } val names = range
.toList()) { .mapNotNull { (text, r) ->
viewModel.changeRange(itemId) r to (text?.asStringNull(ctx) ?: return@mapNotNull null)
}
view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) ->
index to name
}) {
viewModel.changeRange(names[itemId].first)
} }
} }
} }
}
observe(viewModel.seasonSelections) { seasonList ->
result_season_button?.setOnClickListener { view ->
view?.context?.let { ctx ->
val names = seasonList
.mapNotNull { (text, r) ->
r to (text?.asStringNull(ctx) ?: return@mapNotNull null)
}
view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) ->
index to name
}) {
viewModel.changeSeason(names[itemId].first)
}
}
}
}
result_cast_items?.setOnFocusChangeListener { _, hasFocus ->
// Always escape focus
if (hasFocus) result_bookmark_button?.requestFocus()
}
result_sync_set_score?.setOnClickListener { result_sync_set_score?.setOnClickListener {
syncModel.publishUserData() syncModel.publishUserData()
} }
observe(viewModel.episodesCount) { count -> observe(viewModel.episodesCountText) { count ->
if (count < 0) { result_episodes_text.setText(count)
result_episodes_text?.isVisible = false
} else {
// result_episodes_text?.isVisible = true
result_episodes_text?.text =
"$count ${if (count == 1) getString(R.string.episode) else getString(R.string.episodes)}"
}
} }
observe(viewModel.id) { observe(viewModel.trailers) { trailers ->
currentId = it setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet!
}
observe(viewModel.recommendations) { recommendations ->
setRecommendations(recommendations, null)
} }
observe(viewModel.page) { data -> observe(viewModel.page) { data ->
@ -1778,17 +1707,11 @@ class ResultFragment : ResultTrailerPlayer() {
result_meta_rating.setText(d.ratingText) result_meta_rating.setText(d.ratingText)
result_description.setTextHtml(d.plotText) result_description.setTextHtml(d.plotText)
result_cast_text.setText(d.actorsText) result_cast_text.setText(d.actorsText)
setRecommendations.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)
if(!d.posterUrl.isNullOrBlank()) {
result_poster?.setImage(d.posterUrl, d.posterHeaders)
} else {
result_poster?.setImageResource(R.drawable.default_cover)
}
result_cast_items?.isVisible = d.actors != null result_cast_items?.isVisible = d.actors != null
(result_cast_items?.adapter as ActorAdaptor?)?.apply { (result_cast_items?.adapter as ActorAdaptor?)?.apply {
@ -1821,11 +1744,6 @@ class ResultFragment : ResultTrailerPlayer() {
} }
} }
setRecommendations(d.recommendations, null)
setActors(d.actors)
setNextEpisode(if (d is EpisodeResponse) d.nextAiring else null)
setTrailers(d.trailers.flatMap { it.mirros }) // I dont care about subtitles yet!
if (syncModel.addSyncs(d.syncData)) { if (syncModel.addSyncs(d.syncData)) {
syncModel.updateMetaAndUser() syncModel.updateMetaAndUser()
syncModel.updateSynced() syncModel.updateSynced()
@ -1833,70 +1751,20 @@ class ResultFragment : ResultTrailerPlayer() {
syncModel.addFromUrl(d.url) syncModel.addFromUrl(d.url)
} }
result_meta_site?.text = d.apiName result_play_movie.setText(d.playMovieText)
val posterImageLink = d.posterUrl
if (!posterImageLink.isNullOrEmpty()) {
//result_poster_blur?.setImageBlur(posterImageLink, 10, 3, d.posterHeaders) result_description?.setOnClickListener { view ->
//Full screen view of Poster image view.context?.let { ctx ->
if (context?.isTrueTvSettings() == false) // Poster not clickable on tv
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(posterImageLink)
setOnClickListener {
sourceDialog.dismissSafe()
}
}
}
}
} catch (e: Exception) {
logError(e)
}
}
} else {
result_poster?.setImageResource(R.drawable.default_cover)
//result_poster_blur?.setImageResource(R.drawable.default_cover)
}
result_poster_holder?.visibility = VISIBLE
result_play_movie?.text =
if (d.typeText == TvType.Live) getString(R.string.play_livestream_button) else getString(
R.string.play_movie_button
)
//result_plot_header?.text =
// if (d.type == TvType.Torrent) getString(R.string.torrent_plot) else getString(R.string.result_plot)
val syno = d.plot
if (!syno.isNullOrEmpty()) {
result_description?.setOnClickListener {
val builder: AlertDialog.Builder = val builder: AlertDialog.Builder =
AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
builder.setMessage(syno.html()) builder.setMessage(d.plotText.asString(ctx).html())
.setTitle(if (d.typeText == TvType.Torrent) R.string.torrent_plot else R.string.result_plot) .setTitle(d.plotHeaderText.asString(ctx))
.show() .show()
} }
result_description?.text = syno.html()
} else {
result_description?.text =
if (d.typeText == TvType.Torrent) getString(R.string.torrent_no_plot) else getString(
R.string.normal_no_plot
)
} }
result_tag?.removeAllViews() result_tag?.removeAllViews()
//result_tag_holder?.visibility = GONE
// result_status.visibility = GONE
d.comingSoon.let { soon -> d.comingSoon.let { soon ->
result_coming_soon?.isVisible = soon result_coming_soon?.isVisible = soon
@ -1918,6 +1786,8 @@ class ResultFragment : ResultTrailerPlayer() {
} }
} }
//TODO FIX
/*
if (d.typeText.isMovieType()) { if (d.typeText.isMovieType()) {
val hasDownloadSupport = api.hasDownloadSupport val hasDownloadSupport = api.hasDownloadSupport
lateFixDownloadButton(true) lateFixDownloadButton(true)
@ -1942,11 +1812,6 @@ class ResultFragment : ResultTrailerPlayer() {
return@setOnLongClickListener true return@setOnLongClickListener true
} }
// result_options.setOnClickListener {
// val card = currentEpisodes?.first() ?: return@setOnClickListener
// handleAction(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
// }
result_movie_progress_downloaded_holder?.isVisible = hasDownloadSupport result_movie_progress_downloaded_holder?.isVisible = hasDownloadSupport
if (hasDownloadSupport) { if (hasDownloadSupport) {
val localId = d.getId() val localId = d.getId()
@ -2045,19 +1910,7 @@ class ResultFragment : ResultTrailerPlayer() {
} else { } else {
lateFixDownloadButton(false) lateFixDownloadButton(false)
} }
*/
when (d) {
is AnimeLoadResponse -> {
// val preferEnglish = true
//val titleName = (if (preferEnglish) d.engName else d.japName) ?: d.name
val titleName = d.name
result_title.text = titleName
//result_toolbar.title = titleName
}
else -> result_title.text = d.name
}
} }
is Resource.Failure -> { is Resource.Failure -> {
result_error_text.text = url?.plus("\n") + data.errorString result_error_text.text = url?.plus("\n") + data.errorString
@ -2091,7 +1944,7 @@ class ResultFragment : ResultTrailerPlayer() {
val tempUrl = url val tempUrl = url
if (tempUrl != null) { if (tempUrl != null) {
result_reload_connectionerror.setOnClickListener { result_reload_connectionerror.setOnClickListener {
viewModel.load(tempUrl, apiName, showFillers) viewModel.load(tempUrl, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX
} }
result_reload_connection_open_in_browser?.setOnClickListener { result_reload_connection_open_in_browser?.setOnClickListener {
@ -2124,9 +1977,9 @@ class ResultFragment : ResultTrailerPlayer() {
result_meta_site?.isFocusable = false result_meta_site?.isFocusable = false
} }
if (restart || viewModel.result.value == null) { if (restart || !viewModel.hasLoaded()) {
//viewModel.clear() //viewModel.clear()
viewModel.load(tempUrl, apiName, showFillers) viewModel.load(tempUrl, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX
} }
} }
} }

View file

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.ui.result package com.lagradost.cloudstream3.ui.result
import android.util.Log
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@ -7,16 +8,18 @@ import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
import com.lagradost.cloudstream3.animeproviders.GogoanimeProvider import com.lagradost.cloudstream3.animeproviders.GogoanimeProvider
import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider
import com.lagradost.cloudstream3.metaproviders.SyncRedirector import com.lagradost.cloudstream3.metaproviders.SyncRedirector
import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.player.IGenerator import com.lagradost.cloudstream3.ui.player.IGenerator
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.FillerEpisodeCheck
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -55,8 +58,20 @@ 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,
) )
fun txt(status: DubStatus?): UiText? {
return txt(
when (status) {
DubStatus.Dubbed -> R.string.app_dubbed_text
DubStatus.Subbed -> R.string.app_subbed_text
else -> null
}
)
}
fun LoadResponse.toResultData(repo: APIRepository): ResultData { fun LoadResponse.toResultData(repo: APIRepository): ResultData {
debugAssert({ repo.name == apiName }) { debugAssert({ repo.name == apiName }) {
"Api returned wrong apiName" "Api returned wrong apiName"
@ -101,6 +116,20 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
} }
return ResultData( return ResultData(
plotHeaderText = txt(
when (this.type) {
TvType.Torrent -> R.string.torrent_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(
@ -174,6 +203,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 currentMeta: SyncAPI.SyncResult? = null
private var currentSync: Map<String, String>? = null
private var currentIndex: EpisodeIndexer? = null private var currentIndex: EpisodeIndexer? = null
private var currentRange: EpisodeRange? = null private var currentRange: EpisodeRange? = null
private var currentShowFillers: Boolean = false private var currentShowFillers: Boolean = false
@ -193,20 +224,42 @@ class ResultViewModel2 : ViewModel() {
MutableLiveData(Resource.Loading()) MutableLiveData(Resource.Loading())
val episodes: LiveData<Resource<List<ResultEpisode>>> = _episodes val episodes: LiveData<Resource<List<ResultEpisode>>> = _episodes
private val _episodesCount: MutableLiveData<Int> = private val _episodesCountText: MutableLiveData<UiText?> =
MutableLiveData(0) MutableLiveData(null)
val episodesCount: LiveData<Int> = _episodesCount val episodesCountText: LiveData<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
private val _dubStatus: MutableLiveData<DubStatus?> = MutableLiveData(null)
val dubStatus: LiveData<DubStatus?> = _dubStatus
private val _dubSubSelections: MutableLiveData<List<DubStatus>> = MutableLiveData(emptyList()) private val _dubSubSelections: MutableLiveData<List<Pair<UiText?, DubStatus>>> =
val dubSubSelections: LiveData<List<DubStatus>> = _dubSubSelections MutableLiveData(emptyList())
val dubSubSelections: LiveData<List<Pair<UiText?, DubStatus>>> = _dubSubSelections
private val _rangeSelections: MutableLiveData<List<Pair<UiText?, EpisodeRange>>> = MutableLiveData(emptyList())
val rangeSelections: LiveData<List<Pair<UiText?, EpisodeRange>>> = _rangeSelections
private val _seasonSelections: MutableLiveData<List<Pair<UiText?, Int>>> = 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 _selectedSeason: MutableLiveData<UiText?> =
MutableLiveData(null)
val selectedSeason: LiveData<UiText?> = _selectedSeason
private val _selectedDubStatus: MutableLiveData<UiText?> = MutableLiveData(null)
val selectedDubStatus: LiveData<UiText?> = _selectedDubStatus
companion object { companion object {
const val TAG = "RVM2"
private const val EPISODE_RANGE_SIZE = 50 private const val EPISODE_RANGE_SIZE = 50
private const val EPISODE_RANGE_OVERLOAD = 60 private const val EPISODE_RANGE_OVERLOAD = 60
@ -324,6 +377,102 @@ class ResultViewModel2 : ViewModel() {
} }
} }
private suspend fun applyMeta(
resp: LoadResponse,
meta: SyncAPI.SyncResult?,
syncs: Map<String, String>? = null
): Pair<LoadResponse, Boolean> {
if (meta == null) return resp to false
var updateEpisodes = false
val out = resp.apply {
Log.i(ResultViewModel.TAG, "applyMeta")
duration = duration ?: meta.duration
rating = rating ?: meta.publicScore
tags = tags ?: meta.genres
plot = if (plot.isNullOrBlank()) meta.synopsis else plot
posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
actors = actors ?: meta.actors
if (this is EpisodeResponse) {
nextAiring = nextAiring ?: meta.nextAiring
}
for ((k, v) in syncs ?: emptyMap()) {
syncData[k] = v
}
val realRecommendations = ArrayList<SearchResponse>()
val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name)
meta.recommendations?.forEach { rec ->
apiNames.forEach { name ->
realRecommendations.add(rec.copy(apiName = name))
}
}
recommendations = recommendations?.union(realRecommendations)?.toList()
?: realRecommendations
argamap({
addTrailer(meta.trailers)
}, {
if (this !is AnimeLoadResponse) return@argamap
val map =
Kitsu.getEpisodesDetails(getMalId(), getAniListId(), isResponseRequired = false)
if (map.isNullOrEmpty()) return@argamap
updateEpisodes = DubStatus.values().map { dubStatus ->
val current =
this.episodes[dubStatus]?.mapIndexed { index, episode ->
episode.apply {
this.episode = this.episode ?: (index + 1)
}
}?.sortedBy { it.episode ?: 0 }?.toMutableList()
if (current.isNullOrEmpty()) return@map false
val episodeNumbers = current.map { ep -> ep.episode!! }
var updateCount = 0
map.forEach { (episode, node) ->
episodeNumbers.binarySearch(episode).let { index ->
current.getOrNull(index)?.let { currentEp ->
current[index] = currentEp.apply {
updateCount++
val currentBack = this
this.description = this.description ?: node.description?.en
this.name = this.name ?: node.titles?.canonical
this.episode = this.episode ?: node.num ?: episodeNumbers[index]
this.posterUrl = this.posterUrl ?: node.thumbnail?.original?.url
}
}
}
}
this.episodes[dubStatus] = current
updateCount > 0
}.any { it }
})
}
return out to updateEpisodes
}
fun setMeta(meta: SyncAPI.SyncResult, syncs: Map<String, String>?) =
viewModelScope.launch {
Log.i(TAG, "setMeta")
currentMeta = meta
currentSync = syncs
val (value, updateEpisodes) = Coroutines.ioWork {
currentResponse?.let { resp ->
return@ioWork applyMeta(resp, meta, syncs)
}
return@ioWork null to null
}
postSuccessful(
value ?: return@launch,
currentRepo ?: return@launch,
updateEpisodes ?: return@launch,
false
)
}
private suspend fun updateFillers(name: String) { private suspend fun updateFillers(name: String) {
fillers = fillers =
try { try {
@ -343,6 +492,10 @@ class ResultViewModel2 : ViewModel() {
postEpisodeRange(currentIndex, range) postEpisodeRange(currentIndex, range)
} }
fun changeSeason(season: Int) {
postEpisodeRange(currentIndex?.copy(season = season), currentRange)
}
private fun getEpisodes(indexer: EpisodeIndexer, range: EpisodeRange): List<ResultEpisode> { private fun getEpisodes(indexer: EpisodeIndexer, range: EpisodeRange): List<ResultEpisode> {
//TODO ADD GENERATOR //TODO ADD GENERATOR
@ -377,9 +530,34 @@ class ResultViewModel2 : ViewModel() {
return return
} }
val size = currentEpisodes[indexer]?.size
_episodesCountText.postValue(
txt(
R.string.episode_format,
if (size == 1) R.string.episode else R.string.episodes,
size
)
)
currentIndex = indexer currentIndex = indexer
currentRange = range currentRange = range
_selectedSeason.postValue(
when (indexer.season) {
0 -> txt(R.string.no_season)
else -> txt(R.string.season_format, R.string.season, indexer.season) //TODO FIX
}
)
_selectedRange.postValue(
if ((currentRanges[indexer]?.size ?: 0) > 1) {
txt(R.string.episodes_range, range.startEpisode, range.endEpisode)
} else {
null
}
)
_selectedDubStatus.postValue(txt(indexer.dubStatus))
//TODO SET KEYS //TODO SET KEYS
preferStartEpisode = range.startEpisode preferStartEpisode = range.startEpisode
preferStartSeason = indexer.season preferStartSeason = indexer.season
@ -587,10 +765,13 @@ class ResultViewModel2 : ViewModel() {
// this instantly updates the metadata on the page // this instantly updates the metadata on the page
private fun postPage(loadResponse: LoadResponse, apiRepository: APIRepository) { private fun postPage(loadResponse: LoadResponse, apiRepository: APIRepository) {
_recommendations.postValue(loadResponse.recommendations ?: emptyList())
_page.postValue(Resource.Success(loadResponse.toResultData(apiRepository))) _page.postValue(Resource.Success(loadResponse.toResultData(apiRepository)))
_trailers.postValue(loadResponse.trailers) _trailers.postValue(loadResponse.trailers)
} }
fun hasLoaded() = currentResponse != null
fun load( fun load(
url: String, url: String,
apiName: String, apiName: String,
@ -647,7 +828,9 @@ class ResultViewModel2 : ViewModel() {
_page.postValue(data) _page.postValue(data)
} }
is Resource.Success -> { is Resource.Success -> {
val loadResponse = data.value val loadResponse = Coroutines.ioWork {
applyMeta(data.value, currentMeta, currentSync).first
}
val mainId = loadResponse.getId() val mainId = loadResponse.getId()
AcraApplication.setKey( AcraApplication.setKey(

View file

@ -7,6 +7,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.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
@ -18,7 +19,12 @@ sealed class UiText {
) : UiText() ) : UiText()
fun asStringNull(context: Context?): String? { fun asStringNull(context: Context?): String? {
try {
return asString(context ?: return null) return asString(context ?: return null)
} catch (e: Exception) {
logError(e)
return null
}
} }
fun asString(context: Context): String { fun asString(context: Context): String {

View file

@ -290,15 +290,15 @@
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/result_poster_holder" android:id="@+id/result_poster_holder"
android:layout_width="100dp" android:layout_width="wrap_content"
android:layout_height="140dp" android:layout_height="wrap_content"
app:cardCornerRadius="@dimen/rounded_image_radius"> app:cardCornerRadius="@dimen/rounded_image_radius">
<ImageView <ImageView
android:id="@+id/result_poster" android:id="@+id/result_poster"
android:layout_width="match_parent" android:layout_width="100dp"
android:layout_height="match_parent" android:layout_height="140dp"
android:contentDescription="@string/result_poster_img_des" android:contentDescription="@string/result_poster_img_des"
android:foreground="@drawable/outline_drawable" android:foreground="@drawable/outline_drawable"
android:scaleType="centerCrop" android:scaleType="centerCrop"

View file

@ -286,9 +286,12 @@
</string> </string>
<string name="season">Season</string> <string name="season">Season</string>
<string name="season_format">%s %d</string>
<string name="no_season">No Season</string> <string name="no_season">No Season</string>
<string name="episode">Episode</string> <string name="episode">Episode</string>
<string name="episodes">Episodes</string> <string name="episodes">Episodes</string>
<string name="episodes_range">%d-%d</string>
<string name="episode_format" formatted="true">%d %s</string>
<string name="season_short">S</string> <string name="season_short">S</string>
<string name="episode_short">E</string> <string name="episode_short">E</string>
<string name="no_episodes_found">No Episodes found</string> <string name="no_episodes_found">No Episodes found</string>