From f57b12d89cf05315af5080b6d25f83b9932d1457 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Mon, 1 Aug 2022 04:46:43 +0200 Subject: [PATCH] not done 2 --- .../cloudstream3/ui/result/ResultFragment.kt | 353 +++++------------- .../ui/result/ResultViewModel2.kt | 207 +++++++++- .../cloudstream3/ui/result/UiText.kt | 8 +- app/src/main/res/layout/fragment_result.xml | 8 +- app/src/main/res/values/strings.xml | 3 + 5 files changed, 312 insertions(+), 267 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index e3ec3786..f9309da2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -21,7 +21,6 @@ import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.* -import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.content.FileProvider import androidx.core.view.isGone @@ -39,7 +38,6 @@ import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.APIHolder.getId -import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.AcraApplication.Companion.setKey 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.popupMenuNoIconsAndNoStringRes 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.sanitizeFilename 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.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import java.io.File -import java.util.concurrent.TimeUnit const val START_ACTION_NORMAL = 0 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 private lateinit var viewModel: ResultViewModel2 //by activityViewModels() private lateinit var syncModel: SyncViewModel - private var currentHeaderName: String? = null - private var currentType: TvType? = null - private var currentEpisodes: List? = null - private var downloadButton: EasyDownloadButton? = null - private var syncdata: Map? = null override fun onCreateView( inflater: LayoutInflater, @@ -490,13 +480,6 @@ class ResultFragment : ResultTrailerPlayer() { super.onDestroyView() } - override fun onDestroy() { - //requireActivity().viewModelStore.clear() // REMEMBER THE CLEAR - - - super.onDestroy() - } - override fun onResume() { super.onResume() activity?.let { @@ -563,52 +546,6 @@ class ResultFragment : ResultTrailerPlayer() { var startAction: 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 = emptyList() var currentTrailerIndex = 0 @@ -1339,54 +1276,7 @@ class ResultFragment : ResultTrailerPlayer() { result_episode_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 -> val arrayAdapter = ArrayAdapter(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) } + /* observe(viewModel.episodes) { episodeList -> lateFixDownloadButton(episodeList.size <= 1) // movies can have multible parts but still be *movies* this will fix this var isSeriesVisible = false @@ -1666,7 +1557,7 @@ class ResultFragment : ResultTrailerPlayer() { startAction = null startValue = null } - +*/ observe(viewModel.episodes) { episodes -> when (episodes) { is Resource.Failure -> { @@ -1689,25 +1580,91 @@ class ResultFragment : ResultTrailerPlayer() { } } - observe(viewModel.dubStatus) { status -> - result_dub_select?.apply { - isVisible = status != null - status?.toString()?.let { - text = it + observe(viewModel.selectedSeason) { text -> + result_season_button?.setText(text) + + // If the season button is visible the result season button will be next focus down + if (result_season_button?.isVisible == true) + if (result_series_parent?.isVisible == true) + setFocusUpAndDown(result_resume_series_button, result_season_button) + else + setFocusUpAndDown(result_bookmark_button, result_season_button) + } + + observe(viewModel.selectedDubStatus) { status -> + result_dub_select?.setText(status) + + if (result_dub_select?.isVisible == true) + if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) { + if (result_series_parent?.isVisible == true) + setFocusUpAndDown(result_resume_series_button, result_dub_select) + else + setFocusUpAndDown(result_bookmark_button, result_dub_select) + } + } + + observe(viewModel.selectedRange) { range -> + result_episode_select.setText(range) + + // 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) } - } } // val preferDub = context?.getApiDubstatusSettings()?.all { it == DubStatus.Dubbed } == true observe(viewModel.dubSubSelections) { range -> - result_dub_select?.visibility = if (range.size <= 1) GONE else VISIBLE + result_dub_select.setOnClickListener { view -> + view?.context?.let { ctx -> + view.popupMenuNoIconsAndNoStringRes(range + .mapNotNull { (text, status) -> + Pair( + status.ordinal, + text?.asStringNull(ctx) ?: return@mapNotNull null + ) + }) { + viewModel.changeDubStatus(DubStatus.values()[itemId]) + } + } + } + } - if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) { - if (result_series_parent?.isVisible == true) - setFocusUpAndDown(result_resume_series_button, result_dub_select) - else - setFocusUpAndDown(result_bookmark_button, result_dub_select) + observe(viewModel.rangeSelections) { range -> + result_episode_select.setOnClickListener { view -> + view?.context?.let { ctx -> + val names = range + .mapNotNull { (text, r) -> + 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) + } + } } } @@ -1716,48 +1673,20 @@ class ResultFragment : ResultTrailerPlayer() { if (hasFocus) result_bookmark_button?.requestFocus() } - result_dub_select.setOnClickListener { - val ranges = dubRange - if (ranges != null) { - it.popupMenuNoIconsAndNoStringRes(ranges - .map { status -> - Pair( - status.ordinal, - status.toString() - ) - } - .toList()) { - viewModel.changeDubStatus(DubStatus.values()[itemId]) - } - } - } - - result_episode_select?.setOnClickListener { - val ranges = episodeRanges - if (ranges != null) { - it.popupMenuNoIconsAndNoStringRes(ranges.mapIndexed { index, s -> Pair(index, s) } - .toList()) { - viewModel.changeRange(itemId) - } - } - } - result_sync_set_score?.setOnClickListener { syncModel.publishUserData() } - observe(viewModel.episodesCount) { count -> - if (count < 0) { - 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.episodesCountText) { count -> + result_episodes_text.setText(count) } - observe(viewModel.id) { - currentId = it + observe(viewModel.trailers) { trailers -> + setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet! + } + + observe(viewModel.recommendations) { recommendations -> + setRecommendations(recommendations, null) } observe(viewModel.page) { data -> @@ -1778,17 +1707,11 @@ class ResultFragment : ResultTrailerPlayer() { result_meta_rating.setText(d.ratingText) result_description.setTextHtml(d.plotText) result_cast_text.setText(d.actorsText) - setRecommendations.setText(d.nextAiringEpisode) + result_next_airing.setText(d.nextAiringEpisode) result_next_airing_time.setText(d.nextAiringDate) 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?.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)) { syncModel.updateMetaAndUser() syncModel.updateSynced() @@ -1833,70 +1751,20 @@ class ResultFragment : ResultTrailerPlayer() { syncModel.addFromUrl(d.url) } - result_meta_site?.text = d.apiName - val posterImageLink = d.posterUrl - if (!posterImageLink.isNullOrEmpty()) { + result_play_movie.setText(d.playMovieText) - //result_poster_blur?.setImageBlur(posterImageLink, 10, 3, d.posterHeaders) - //Full screen view of Poster image - 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(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 { + result_description?.setOnClickListener { view -> + view.context?.let { ctx -> val builder: AlertDialog.Builder = - AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) - builder.setMessage(syno.html()) - .setTitle(if (d.typeText == TvType.Torrent) R.string.torrent_plot else R.string.result_plot) + AlertDialog.Builder(ctx, R.style.AlertDialogCustom) + builder.setMessage(d.plotText.asString(ctx).html()) + .setTitle(d.plotHeaderText.asString(ctx)) .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_holder?.visibility = GONE - // result_status.visibility = GONE d.comingSoon.let { soon -> result_coming_soon?.isVisible = soon @@ -1905,7 +1773,7 @@ class ResultFragment : ResultTrailerPlayer() { val tags = d.tags result_tag_holder?.isVisible = tags.isNotEmpty() - if (tags.isNotEmpty()) { + if (tags.isNotEmpty()) { //result_tag_holder?.visibility = VISIBLE val isOnTv = context?.isTrueTvSettings() == true for ((index, tag) in tags.withIndex()) { @@ -1918,6 +1786,8 @@ class ResultFragment : ResultTrailerPlayer() { } } + //TODO FIX + /* if (d.typeText.isMovieType()) { val hasDownloadSupport = api.hasDownloadSupport lateFixDownloadButton(true) @@ -1942,11 +1812,6 @@ class ResultFragment : ResultTrailerPlayer() { 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 if (hasDownloadSupport) { val localId = d.getId() @@ -2045,19 +1910,7 @@ class ResultFragment : ResultTrailerPlayer() { } else { 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 -> { result_error_text.text = url?.plus("\n") + data.errorString @@ -2091,7 +1944,7 @@ class ResultFragment : ResultTrailerPlayer() { val tempUrl = url if (tempUrl != null) { 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 { @@ -2124,9 +1977,9 @@ class ResultFragment : ResultTrailerPlayer() { result_meta_site?.isFocusable = false } - if (restart || viewModel.result.value == null) { + if (restart || !viewModel.hasLoaded()) { //viewModel.clear() - viewModel.load(tempUrl, apiName, showFillers) + viewModel.load(tempUrl, apiName, showFillers, DubStatus.Dubbed, 0, 0) //TODO FIX } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 4dc5db6c..303ec20e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.result +import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -7,16 +8,18 @@ import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getId 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.NineAnimeProvider import com.lagradost.cloudstream3.metaproviders.SyncRedirector 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.player.IGenerator -import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE -import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.FillerEpisodeCheck -import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import com.lagradost.cloudstream3.utils.* import kotlinx.coroutines.launch import java.util.concurrent.TimeUnit @@ -55,8 +58,20 @@ data class ResultData( val yearText: UiText?, val nextAiringDate: 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 { debugAssert({ repo.name == apiName }) { "Api returned wrong apiName" @@ -101,6 +116,20 @@ fun LoadResponse.toResultData(repo: APIRepository): 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, nextAiringEpisode = nextAiringEpisode, posterImage = img( @@ -174,6 +203,8 @@ class ResultViewModel2 : ViewModel() { /** map>> */ private var currentEpisodes: Map> = mapOf() private var currentRanges: Map> = mapOf() + private var currentMeta: SyncAPI.SyncResult? = null + private var currentSync: Map? = null private var currentIndex: EpisodeIndexer? = null private var currentRange: EpisodeRange? = null private var currentShowFillers: Boolean = false @@ -193,20 +224,42 @@ class ResultViewModel2 : ViewModel() { MutableLiveData(Resource.Loading()) val episodes: LiveData>> = _episodes - private val _episodesCount: MutableLiveData = - MutableLiveData(0) - val episodesCount: LiveData = _episodesCount + private val _episodesCountText: MutableLiveData = + MutableLiveData(null) + val episodesCountText: LiveData = _episodesCountText private val _trailers: MutableLiveData> = MutableLiveData(mutableListOf()) val trailers: LiveData> = _trailers - private val _dubStatus: MutableLiveData = MutableLiveData(null) - val dubStatus: LiveData = _dubStatus - private val _dubSubSelections: MutableLiveData> = MutableLiveData(emptyList()) - val dubSubSelections: LiveData> = _dubSubSelections + private val _dubSubSelections: MutableLiveData>> = + MutableLiveData(emptyList()) + val dubSubSelections: LiveData>> = _dubSubSelections + + private val _rangeSelections: MutableLiveData>> = MutableLiveData(emptyList()) + val rangeSelections: LiveData>> = _rangeSelections + + private val _seasonSelections: MutableLiveData>> = MutableLiveData(emptyList()) + val seasonSelections: LiveData>> = _seasonSelections + + + private val _recommendations: MutableLiveData> = + MutableLiveData(emptyList()) + val recommendations: LiveData> = _recommendations + + private val _selectedRange: MutableLiveData = + MutableLiveData(null) + val selectedRange: LiveData = _selectedRange + + private val _selectedSeason: MutableLiveData = + MutableLiveData(null) + val selectedSeason: LiveData = _selectedSeason + + private val _selectedDubStatus: MutableLiveData = MutableLiveData(null) + val selectedDubStatus: LiveData = _selectedDubStatus companion object { + const val TAG = "RVM2" private const val EPISODE_RANGE_SIZE = 50 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? = null + ): Pair { + 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() + 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?) = + 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) { fillers = try { @@ -343,6 +492,10 @@ class ResultViewModel2 : ViewModel() { postEpisodeRange(currentIndex, range) } + fun changeSeason(season: Int) { + postEpisodeRange(currentIndex?.copy(season = season), currentRange) + } + private fun getEpisodes(indexer: EpisodeIndexer, range: EpisodeRange): List { //TODO ADD GENERATOR @@ -377,9 +530,34 @@ class ResultViewModel2 : ViewModel() { 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 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 preferStartEpisode = range.startEpisode preferStartSeason = indexer.season @@ -587,10 +765,13 @@ class ResultViewModel2 : ViewModel() { // this instantly updates the metadata on the page private fun postPage(loadResponse: LoadResponse, apiRepository: APIRepository) { + _recommendations.postValue(loadResponse.recommendations ?: emptyList()) _page.postValue(Resource.Success(loadResponse.toResultData(apiRepository))) _trailers.postValue(loadResponse.trailers) } + fun hasLoaded() = currentResponse != null + fun load( url: String, apiName: String, @@ -647,7 +828,9 @@ class ResultViewModel2 : ViewModel() { _page.postValue(data) } is Resource.Success -> { - val loadResponse = data.value + val loadResponse = Coroutines.ioWork { + applyMeta(data.value, currentMeta, currentSync).first + } val mainId = loadResponse.getId() AcraApplication.setKey( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt index e55fe873..9e456156 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt @@ -7,6 +7,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.view.isGone import androidx.core.view.isVisible +import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.UIHelper.setImage @@ -18,7 +19,12 @@ sealed class UiText { ) : UiText() fun asStringNull(context: Context?): String? { - return asString(context ?: return null) + try { + return asString(context ?: return null) + } catch (e: Exception) { + logError(e) + return null + } } fun asString(context: Context): String { diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 228a3731..11513553 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -290,15 +290,15 @@ Season + %s %d No Season Episode Episodes + %d-%d + %d %s S E No Episodes found