From ed0d374721839efe1bf9a259f9b69d6078432aea Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Wed, 19 Jul 2023 01:51:17 +0200 Subject: [PATCH] removed synthetic by removing ResultFragment --- app/build.gradle.kts | 1 - .../cloudstream3/ui/result/ResultFragment.kt | 371 +++++------------- .../ui/result/ResultFragmentPhone.kt | 201 ++++++++-- .../ui/result/ResultFragmentTv.kt | 207 ++++++++-- .../ui/result/ResultViewModel2.kt | 12 +- .../lagradost/cloudstream3/utils/AppUtils.kt | 10 + .../main/res/layout/fragment_result_tv.xml | 12 +- 7 files changed, 459 insertions(+), 355 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b2061572..f4886258 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,7 +7,6 @@ plugins { id("com.android.application") id("kotlin-android") id("kotlin-kapt") - id("kotlin-android-extensions") id("org.jetbrains.dokka") } 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 3dfb1f9c..7617bc11 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 @@ -1,94 +1,18 @@ package com.lagradost.cloudstream3.ui.result -import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import android.content.Intent.* -import android.net.Uri import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import androidx.appcompat.app.AlertDialog -import androidx.core.view.isGone -import androidx.core.view.isVisible -import androidx.lifecycle.ViewModelProvider +import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager -import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.chip.Chip -import com.google.android.material.chip.ChipDrawable import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings -import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.DubStatus -import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType -import com.lagradost.cloudstream3.mvvm.* -import com.lagradost.cloudstream3.syncproviders.providers.Kitsu -import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD -import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick -import com.lagradost.cloudstream3.ui.player.FullScreenPlayer import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings -import com.lagradost.cloudstream3.utils.* -import com.lagradost.cloudstream3.utils.AppUtils.getNameFull -import com.lagradost.cloudstream3.utils.AppUtils.html -import com.lagradost.cloudstream3.utils.AppUtils.loadCache -import com.lagradost.cloudstream3.utils.AppUtils.openBrowser -import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.getVideoWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos -import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute -import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe -import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard -import kotlinx.android.synthetic.main.fragment_result.download_button -import kotlinx.android.synthetic.main.fragment_result.result_cast_items -import kotlinx.android.synthetic.main.fragment_result.result_cast_text -import kotlinx.android.synthetic.main.fragment_result.result_coming_soon -import kotlinx.android.synthetic.main.fragment_result.result_data_holder -import kotlinx.android.synthetic.main.fragment_result.result_description -import kotlinx.android.synthetic.main.fragment_result.result_dub_select -import kotlinx.android.synthetic.main.fragment_result.result_episode_loading -import kotlinx.android.synthetic.main.fragment_result.result_episode_select -import kotlinx.android.synthetic.main.fragment_result.result_episodes -import kotlinx.android.synthetic.main.fragment_result.result_error_text -import kotlinx.android.synthetic.main.fragment_result.result_finish_loading -import kotlinx.android.synthetic.main.fragment_result.result_info -import kotlinx.android.synthetic.main.fragment_result.result_loading -import kotlinx.android.synthetic.main.fragment_result.result_loading_error -import kotlinx.android.synthetic.main.fragment_result.result_meta_duration -import kotlinx.android.synthetic.main.fragment_result.result_meta_rating -import kotlinx.android.synthetic.main.fragment_result.result_meta_site -import kotlinx.android.synthetic.main.fragment_result.result_meta_type -import kotlinx.android.synthetic.main.fragment_result.result_meta_year -import kotlinx.android.synthetic.main.fragment_result.result_next_airing -import kotlinx.android.synthetic.main.fragment_result.result_next_airing_time -import kotlinx.android.synthetic.main.fragment_result.result_no_episodes -import kotlinx.android.synthetic.main.fragment_result.result_play_movie -import kotlinx.android.synthetic.main.fragment_result.result_poster -import kotlinx.android.synthetic.main.fragment_result.result_poster_background -import kotlinx.android.synthetic.main.fragment_result.result_poster_holder -import kotlinx.android.synthetic.main.fragment_result.result_reload_connection_open_in_browser -import kotlinx.android.synthetic.main.fragment_result.result_reload_connectionerror -import kotlinx.android.synthetic.main.fragment_result.result_resume_parent -import kotlinx.android.synthetic.main.fragment_result.result_resume_progress_holder -import kotlinx.android.synthetic.main.fragment_result.result_resume_series_button -import kotlinx.android.synthetic.main.fragment_result.result_resume_series_progress -import kotlinx.android.synthetic.main.fragment_result.result_resume_series_progress_text -import kotlinx.android.synthetic.main.fragment_result.result_resume_series_title -import kotlinx.android.synthetic.main.fragment_result.result_season_button -import kotlinx.android.synthetic.main.fragment_result.result_tag -import kotlinx.android.synthetic.main.fragment_result.result_tag_holder -import kotlinx.android.synthetic.main.fragment_result.result_title -import kotlinx.android.synthetic.main.fragment_result.result_vpn -import kotlinx.android.synthetic.main.fragment_result_tv.result_resume_series_button_play -import kotlinx.android.synthetic.main.fragment_result_tv.temporary_no_focus -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking +import com.lagradost.cloudstream3.utils.Event const val START_ACTION_RESUME_LATEST = 1 const val START_ACTION_LOAD_EP = 2 @@ -188,118 +112,113 @@ fun ResultEpisode.getWatchProgress(): Float { return (getDisplayPosition() / 1000).toFloat() / (duration / 1000).toFloat() } -open class ResultFragment : FullScreenPlayer() { - companion object { - const val URL_BUNDLE = "url" - const val API_NAME_BUNDLE = "apiName" - const val SEASON_BUNDLE = "season" - const val EPISODE_BUNDLE = "episode" - const val START_ACTION_BUNDLE = "startAction" - const val START_VALUE_BUNDLE = "startValue" - const val RESTART_BUNDLE = "restart" +object ResultFragment { + private const val URL_BUNDLE = "url" + private const val API_NAME_BUNDLE = "apiName" + private const val SEASON_BUNDLE = "season" + private const val EPISODE_BUNDLE = "episode" + private const val START_ACTION_BUNDLE = "startAction" + private const val START_VALUE_BUNDLE = "startValue" + private const val RESTART_BUNDLE = "restart" - fun newInstance( - card: SearchResponse, startAction: Int = 0, startValue: Int? = null - ): Bundle { - return Bundle().apply { - putString(URL_BUNDLE, card.url) - putString(API_NAME_BUNDLE, card.apiName) - if (card is DataStoreHelper.ResumeWatchingResult) { - if (card.season != null) - putInt(SEASON_BUNDLE, card.season) - if (card.episode != null) - putInt(EPISODE_BUNDLE, card.episode) - } - putInt(START_ACTION_BUNDLE, startAction) - if (startValue != null) - putInt(START_VALUE_BUNDLE, startValue) - - - putBoolean(RESTART_BUNDLE, true) + fun newInstance( + card: SearchResponse, startAction: Int = 0, startValue: Int? = null + ): Bundle { + return Bundle().apply { + putString(URL_BUNDLE, card.url) + putString(API_NAME_BUNDLE, card.apiName) + if (card is DataStoreHelper.ResumeWatchingResult) { + if (card.season != null) + putInt(SEASON_BUNDLE, card.season) + if (card.episode != null) + putInt(EPISODE_BUNDLE, card.episode) } - } - - fun newInstance( - url: String, - apiName: String, - startAction: Int = 0, - startValue: Int = 0 - ): Bundle { - return Bundle().apply { - putString(URL_BUNDLE, url) - putString(API_NAME_BUNDLE, apiName) - putInt(START_ACTION_BUNDLE, startAction) + putInt(START_ACTION_BUNDLE, startAction) + if (startValue != null) putInt(START_VALUE_BUNDLE, startValue) - putBoolean(RESTART_BUNDLE, true) - } - } - fun updateUI() { - updateUIListener?.invoke() - } - private var updateUIListener: (() -> Unit)? = null + putBoolean(RESTART_BUNDLE, true) + } } - open fun setTrailers(trailers: List?) {} + fun newInstance( + url: String, + apiName: String, + startAction: Int = 0, + startValue: Int = 0 + ): Bundle { + return Bundle().apply { + putString(URL_BUNDLE, url) + putString(API_NAME_BUNDLE, apiName) + putInt(START_ACTION_BUNDLE, startAction) + putInt(START_VALUE_BUNDLE, startValue) + putBoolean(RESTART_BUNDLE, true) + } + } + + fun updateUI(id: Int? = null) { + // updateUIListener?.invoke() + updateUIEvent.invoke(id) + } + val updateUIEvent = Event() + + //private var updateUIListener: (() -> Unit)? = null + - protected lateinit var viewModel: ResultViewModel2 //by activityViewModels() - protected lateinit var syncModel: SyncViewModel //protected open val resultLayout = R.layout.fragment_result_swipe - override var layout = R.layout.fragment_result_swipe + /* override var layout = R.layout.fragment_result_swipe - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - viewModel = - ViewModelProvider(this)[ResultViewModel2::class.java] - syncModel = - ViewModelProvider(this)[SyncViewModel::class.java] + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { - return super.onCreateView(inflater, container, savedInstanceState) - //return inflater.inflate(resultLayout, container, false) - } + return super.onCreateView(inflater, container, savedInstanceState) + //return inflater.inflate(resultLayout, container, false) + } - override fun onDestroyView() { - updateUIListener = null - super.onDestroyView() - } + override fun onDestroyView() { + updateUIListener = null + super.onDestroyView() + } - override fun onResume() { - afterPluginsLoadedEvent += ::reloadViewModel - super.onResume() - activity?.let { - it.window?.navigationBarColor = - it.colorFromAttribute(R.attr.primaryBlackBackground) - } - } + override fun onResume() { + afterPluginsLoadedEvent += ::reloadViewModel + super.onResume() + activity?.let { + it.window?.navigationBarColor = + it.colorFromAttribute(R.attr.primaryBlackBackground) + } + } - override fun onDestroy() { - afterPluginsLoadedEvent -= ::reloadViewModel - super.onDestroy() - } + override fun onDestroy() { + afterPluginsLoadedEvent -= ::reloadViewModel + super.onDestroy() + } - private fun updateUI() { - syncModel.updateUserData() - viewModel.reloadEpisodes() - } + private fun updateUI() { + syncModel.updateUserData() + viewModel.reloadEpisodes() + }*/ data class StoredData( - val url: String?, + val url: String, val apiName: String, val showFillers: Boolean, val dubStatus: DubStatus, val start: AutoResume?, - val playerAction: Int + val playerAction: Int, + val restart : Boolean, ) - fun getStoredData(context: Context): StoredData? { + fun Fragment.getStoredData(): StoredData? { + val context = this.context ?: this.activity ?: return null val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) - val url = arguments?.getString(URL_BUNDLE) + val url = arguments?.getString(URL_BUNDLE) ?: return null val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return null val showFillers = settingsManager.getBoolean(context.getString(R.string.show_fillers_key), false) @@ -310,6 +229,11 @@ open class ResultFragment : FullScreenPlayer() { val playerAction = getPlayerAction(context) + val restart = arguments?.getBoolean(RESTART_BUNDLE) ?: false + if (restart) { + arguments?.putBoolean(RESTART_BUNDLE, false) + } + val start = startAction?.let { action -> val startValue = arguments?.getInt(START_VALUE_BUNDLE) val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE) @@ -324,10 +248,10 @@ open class ResultFragment : FullScreenPlayer() { season = resumeSeason ) } - return StoredData(url, apiName, showFillers, dubStatus, start, playerAction) + return StoredData(url, apiName, showFillers, dubStatus, start, playerAction, restart) } - private fun reloadViewModel(forceReload: Boolean) { + /*private fun reloadViewModel(forceReload: Boolean) { if (!viewModel.hasLoaded() || forceReload) { val storedData = getStoredData(activity ?: context ?: return) ?: return @@ -346,26 +270,6 @@ open class ResultFragment : FullScreenPlayer() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - result_cast_items?.layoutManager = object : LinearListLayout(view.context) { - override fun onRequestChildFocus( - parent: RecyclerView, - state: RecyclerView.State, - child: View, - focused: View? - ): Boolean { - // Make the cast always focus the first visible item when focused - // from somewhere else. Otherwise it jumps to the last item. - return if (parent.focusedChild == null) { - scrollToPosition(this.findFirstCompletelyVisibleItemPosition()) - true - } else { - super.onRequestChildFocus(parent, state, child, focused) - } - } - }.apply { - this.orientation = RecyclerView.HORIZONTAL - } - result_cast_items?.adapter = ActorAdaptor() updateUIListener = ::updateUI @@ -401,97 +305,7 @@ open class ResultFragment : FullScreenPlayer() { result_season_button?.isFocusableInTouchMode = isTv result_episode_select?.isFocusableInTouchMode = isTv result_dub_select?.isFocusableInTouchMode = isTv - - - observeNullable(viewModel.resumeWatching) { resume -> - if (resume == null) { - result_resume_parent?.isVisible = false - return@observeNullable - } - result_resume_parent?.isVisible = true - resume.progress?.let { progress -> - result_resume_series_title?.apply { - isVisible = !resume.isMovie - text = - if (resume.isMovie) null else activity?.getNameFull( - resume.result.name, - resume.result.episode, - resume.result.season - ) - } - result_resume_series_progress_text?.setText(progress.progressLeft) - result_resume_series_progress?.apply { - isVisible = true - this.max = progress.maxProgress - this.progress = progress.progress - } - result_resume_progress_holder?.isVisible = true - } ?: run { - result_resume_progress_holder?.isVisible = false - result_resume_series_progress?.isVisible = false - result_resume_series_title?.isVisible = false - result_resume_series_progress_text?.isVisible = false - } - - result_resume_series_button?.isVisible = !resume.isMovie - result_resume_series_button_play?.isVisible = !resume.isMovie - - val click = View.OnClickListener { - viewModel.handleAction( - activity, - EpisodeClickEvent( - storedData?.playerAction ?: ACTION_PLAY_EPISODE_IN_PLAYER, - resume.result - ) - ) - } - - result_resume_series_button?.setOnClickListener(click) - result_resume_series_button_play?.setOnClickListener(click) - } - - context?.let { ctx -> - - //result_bookmark_button?.isVisible = ctx.isTvSettings() - - val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) - - - Kitsu.isEnabled = - settingsManager.getBoolean(ctx.getString(R.string.show_kitsu_posters_key), true) - if (storedData?.url != null) { - result_reload_connectionerror.setOnClickListener { - viewModel.load( - activity, - storedData.url, - storedData.apiName, - storedData.showFillers, - storedData.dubStatus, - storedData.start - ) - } - - result_reload_connection_open_in_browser?.setOnClickListener { - val i = Intent(ACTION_VIEW) - i.data = Uri.parse(storedData.url) - try { - startActivity(i) - } catch (e: Exception) { - logError(e) - } - } - - // bloats the navigation on tv - if (!isTrueTvSettings()) { - result_meta_site?.setOnClickListener { - it.context?.openBrowser(storedData.url) - } - result_meta_site?.isFocusable = true - } else { - result_meta_site?.isFocusable = false - } - if (restart || !viewModel.hasLoaded()) { //viewModel.clear() viewModel.load( @@ -504,6 +318,5 @@ open class ResultFragment : FullScreenPlayer() { ) } } - } - } + }*/ } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index 04fb2614..e4ac13f8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -5,7 +5,6 @@ import android.app.Dialog import android.content.Intent import android.content.res.ColorStateList import android.graphics.Rect -import android.net.Uri import android.os.Build import android.os.Bundle import android.text.Editable @@ -23,6 +22,8 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView import androidx.core.widget.doOnTextChanged +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.RecyclerView import com.discord.panels.OverlappingPanelsLayout import com.discord.panels.PanelsChildGestureRegionObserver import com.google.android.gms.cast.framework.CastButtonFactory @@ -34,6 +35,7 @@ import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.databinding.FragmentResultBinding @@ -50,11 +52,16 @@ import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup import com.lagradost.cloudstream3.ui.player.CSPlayerEvent +import com.lagradost.cloudstream3.ui.player.FullScreenPlayer import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment +import com.lagradost.cloudstream3.ui.result.ResultFragment.getStoredData +import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchHelper +import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable +import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog @@ -63,6 +70,7 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe +import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.populateChips import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes @@ -70,17 +78,29 @@ import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper -open class ResultFragmentPhone : ResultFragment(), +open class ResultFragmentPhone : FullScreenPlayer(), PanelsChildGestureRegionObserver.GestureRegionsListener { + protected lateinit var viewModel: ResultViewModel2 + protected lateinit var syncModel: SyncViewModel + protected var binding: FragmentResultSwipeBinding? = null protected var resultBinding: FragmentResultBinding? = null protected var recommendationBinding: ResultRecommendationsBinding? = null protected var syncBinding: ResultSyncBinding? = null + + override var layout = R.layout.fragment_result_swipe + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { + viewModel = + ViewModelProvider(this)[ResultViewModel2::class.java] + syncModel = + ViewModelProvider(this)[SyncViewModel::class.java] + updateUIEvent += ::updateUI + val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null FragmentResultSwipeBinding.bind(root).let { bind -> resultBinding = @@ -180,7 +200,7 @@ open class ResultFragmentPhone : ResultFragment(), } - override fun setTrailers(trailers: List?) { + private fun setTrailers(trailers: List?) { context?.updateHasTrailers() if (!LoadResponse.isTrailersEnabled) return currentTrailers = trailers?.sortedBy { -it.quality } ?: emptyList() @@ -196,6 +216,7 @@ open class ResultFragmentPhone : ResultFragment(), } obs.removeGestureRegionsUpdateListener(this) } + updateUIEvent -= ::updateUI binding = null resultBinding = null syncBinding = null @@ -218,48 +239,128 @@ open class ResultFragmentPhone : ResultFragment(), var selectSeason: String? = null - private fun setUrl(url : String?) { - if(url == null) { + private fun setUrl(url: String?) { + if (url == null) { binding?.resultOpenInBrowser?.isVisible = false return } + val valid = url.startsWith("http") + binding?.resultOpenInBrowser?.apply { - isVisible = url.startsWith("http") + isVisible = valid setOnClickListener { - val i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse(url) - try { - startActivity(i) - } catch (e: Exception) { - logError(e) - } + context?.openBrowser(url) } } + + resultBinding?.resultReloadConnectionOpenInBrowser?.setOnClickListener { + view?.context?.openBrowser(url) + } + + resultBinding?.resultMetaSite?.setOnClickListener { + view?.context?.openBrowser(url) + } + } + + private fun reloadViewModel(forceReload: Boolean) { + if (!viewModel.hasLoaded() || forceReload) { + val storedData = getStoredData() ?: return + viewModel.load( + activity, + storedData.url, + storedData.apiName, + storedData.showFillers, + storedData.dubStatus, + storedData.start + ) + } + } + + override fun onResume() { + afterPluginsLoadedEvent += ::reloadViewModel + activity?.let { + it.window?.navigationBarColor = + it.colorFromAttribute(R.attr.primaryBlackBackground) + } + super.onResume() + } + + override fun onStop() { + afterPluginsLoadedEvent -= ::reloadViewModel + super.onStop() + } + + private fun updateUI(id : Int?) { + syncModel.updateUserData() + viewModel.reloadEpisodes() } @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return - super.onViewCreated(view, savedInstanceState) + // ===== setup ===== UIHelper.fixPaddingStatusbar(binding?.resultTopBar) - val storedData = (activity ?: context)?.let { - getStoredData(it) - } + val storedData = getStoredData() ?: return + activity?.window?.decorView?.clearFocus() + activity?.loadCache() + context?.updateHasTrailers() + hideKeyboard() + if (storedData.restart || !viewModel.hasLoaded()) + viewModel.load( + activity, + storedData.url, + storedData.apiName, + storedData.showFillers, + storedData.dubStatus, + storedData.start + ) - setUrl(storedData?.url) - syncModel.addFromUrl(storedData?.url) - - val api = APIHolder.getApiFromNameNull(apiName) + setUrl(storedData.url) + syncModel.addFromUrl(storedData.url) + val api = APIHolder.getApiFromNameNull(storedData.apiName) + PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this) + // ===== ===== ===== resultBinding?.apply { + resultReloadConnectionerror.setOnClickListener { + viewModel.load( + activity, + storedData.url, + storedData.apiName, + storedData.showFillers, + storedData.dubStatus, + storedData.start + ) + } + + resultCastItems.layoutManager = object : LinearListLayout(view.context) { + override fun onRequestChildFocus( + parent: RecyclerView, + state: RecyclerView.State, + child: View, + focused: View? + ): Boolean { + // Make the cast always focus the first visible item when focused + // from somewhere else. Otherwise it jumps to the last item. + return if (parent.focusedChild == null) { + scrollToPosition(this.findFirstCompletelyVisibleItemPosition()) + true + } else { + super.onRequestChildFocus(parent, state, child, focused) + } + } + }.apply { + this.orientation = RecyclerView.HORIZONTAL + } + resultCastItems.adapter = ActorAdaptor() + resultEpisodes.adapter = EpisodeAdapter( api?.hasDownloadSupport == true, { episodeClick -> - viewModel.handleAction(activity, episodeClick) + viewModel.handleAction(episodeClick) }, { downloadClickEvent -> DownloadButtonSetup.handleDownloadClick(downloadClickEvent) @@ -291,6 +392,8 @@ open class ResultFragmentPhone : ResultFragment(), resultBack.setOnClickListener { activity?.popCurrentPage() } + + resultMiniSync.adapter = ImageAdapter( nextFocusDown = R.id.result_sync_set_score, clickCallback = { action -> @@ -368,7 +471,6 @@ open class ResultFragmentPhone : ResultFragment(), } } - PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this) /* result_bookmark_button?.setOnClickListener { @@ -381,6 +483,50 @@ open class ResultFragmentPhone : ResultFragment(), } }*/ + observeNullable(viewModel.resumeWatching) { resume -> + resultBinding?.apply { + if (resume == null) { + resultResumeParent.isVisible = false + return@observeNullable + } + resultResumeParent.isVisible = true + resume.progress?.let { progress -> + resultResumeSeriesTitle.apply { + isVisible = !resume.isMovie + text = + if (resume.isMovie) null else context?.getNameFull( + resume.result.name, + resume.result.episode, + resume.result.season + ) + } + + resultResumeSeriesProgressText.setText(progress.progressLeft) + resultResumeSeriesProgress.apply { + isVisible = true + this.max = progress.maxProgress + this.progress = progress.progress + } + resultResumeProgressHolder.isVisible = true + } ?: run { + resultResumeProgressHolder.isVisible = false + resultResumeSeriesProgress.isVisible = false + resultResumeSeriesTitle.isVisible = false + resultResumeSeriesProgressText.isVisible = false + } + + resultResumeSeriesButton.isVisible = !resume.isMovie + resultResumeSeriesButton.setOnClickListener { + viewModel.handleAction( + EpisodeClickEvent( + storedData.playerAction, //?: ACTION_PLAY_EPISODE_IN_PLAYER, + resume.result + ) + ) + } + } + } + observeNullable(viewModel.subscribeStatus) { isSubscribed -> binding?.resultSubscribe?.isVisible = isSubscribed != null if (isSubscribed == null) return@observeNullable @@ -403,7 +549,7 @@ open class ResultFragmentPhone : ResultFragment(), // no failure? resultEpisodeLoading.isVisible = episodes is Resource.Loading resultEpisodes.isVisible = episodes is Resource.Success - if(episodes is Resource.Success) { + if (episodes is Resource.Success) { (resultEpisodes.adapter as? EpisodeAdapter)?.updateList(episodes.value) } } @@ -419,13 +565,11 @@ open class ResultFragmentPhone : ResultFragment(), resultPlayMovie.setText(text) resultPlayMovie.setOnClickListener { viewModel.handleAction( - activity, EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep) ) } resultPlayMovie.setOnLongClickListener { viewModel.handleAction( - activity, EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep) ) return@setOnLongClickListener true @@ -446,7 +590,6 @@ open class ResultFragmentPhone : ResultFragment(), when (click.action) { DOWNLOAD_ACTION_DOWNLOAD -> { viewModel.handleAction( - activity, EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, ep) ) } @@ -529,7 +672,7 @@ open class ResultFragmentPhone : ResultFragment(), } (data as? Resource.Failure)?.let { data -> - resultErrorText.text = (storedData?.url?.plus("\n") ?: "") + data.errorString + resultErrorText.text = storedData.url.plus("\n") + data.errorString } binding?.resultBookmarkFab?.isVisible = data is Resource.Success diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 66090642..8639af69 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -9,11 +9,14 @@ import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.databinding.FragmentResultTvBinding @@ -24,27 +27,32 @@ import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator import com.lagradost.cloudstream3.ui.player.GeneratorPlayer +import com.lagradost.cloudstream3.ui.result.ResultFragment.getStoredData +import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchHelper +import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.html +import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.Coroutines.main -import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant import com.lagradost.cloudstream3.utils.UIHelper +import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe +import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.setImage import kotlinx.coroutines.delay -class ResultFragmentTv : ResultFragment() { - override var layout = R.layout.fragment_result_tv - +class ResultFragmentTv : Fragment() { + protected lateinit var viewModel: ResultViewModel2 private var binding: FragmentResultTvBinding? = null override fun onDestroyView() { binding = null + updateUIEvent -= ::updateUI super.onDestroyView() } @@ -52,11 +60,18 @@ class ResultFragmentTv : ResultFragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null - binding = FragmentResultTvBinding.bind(root) + ): View { + viewModel = + ViewModelProvider(this)[ResultViewModel2::class.java] + updateUIEvent += ::updateUI - return root + val localBinding = FragmentResultTvBinding.inflate(inflater, container, false) + binding = localBinding + return localBinding.root + } + + private fun updateUI(id : Int?) { + viewModel.reloadEpisodes() } private var currentRecommendations: List = emptyList() @@ -102,25 +117,6 @@ class ResultFragmentTv : ResultFragment() { return focus == binding?.resultRoot } - override fun setTrailers(trailers: List?) { - context?.updateHasTrailers() - if (!LoadResponse.isTrailersEnabled) return - binding?.resultPlayTrailer?.apply { - isGone = trailers.isNullOrEmpty() - setOnClickListener { - if (trailers.isNullOrEmpty()) return@setOnClickListener - activity.navigate( - R.id.global_to_navigation_player, GeneratorPlayer.newInstance( - ExtractorLinkGenerator( - trailers, - emptyList() - ) - ) - ) - } - } - } - private fun setRecommendations(rec: List?, validApiName: String?) { currentRecommendations = rec ?: emptyList() val isInvalid = rec.isNullOrEmpty() @@ -145,15 +141,78 @@ class ResultFragmentTv : ResultFragment() { var loadingDialog: Dialog? = null var popupDialog: Dialog? = null + private fun reloadViewModel(forceReload : Boolean) { + if (!viewModel.hasLoaded() || forceReload) { + val storedData = getStoredData() ?: return + viewModel.load( + activity, + storedData.url, + storedData.apiName, + storedData.showFillers, + storedData.dubStatus, + storedData.start + ) + } + } + + override fun onResume() { + activity?.let { + it.window?.navigationBarColor = + it.colorFromAttribute(R.attr.primaryBlackBackground) + } + afterPluginsLoadedEvent += ::reloadViewModel + super.onResume() + } + + override fun onStop() { + afterPluginsLoadedEvent -= ::reloadViewModel + super.onStop() + } + @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + + // ===== setup ===== + val storedData = getStoredData() ?: return + activity?.window?.decorView?.clearFocus() + activity?.loadCache() + hideKeyboard() + if (storedData.restart || !viewModel.hasLoaded()) + viewModel.load( + activity, + storedData.url, + storedData.apiName, + storedData.showFillers, + storedData.dubStatus, + storedData.start + ) + // ===== ===== ===== + binding?.apply { resultEpisodes.layoutManager = LinearListLayout(resultEpisodes.context).apply { setHorizontal() } + resultReloadConnectionerror.setOnClickListener { + viewModel.load( + activity, + storedData.url, + storedData.apiName, + storedData.showFillers, + storedData.dubStatus, + storedData.start + ) + + } + + resultMetaSite.isFocusable = false + + //resultReloadConnectionOpenInBrowser.setOnClickListener {view -> + // view.context?.openBrowser(storedData?.url ?: return@setOnClickListener, fallbackWebview = true) + //} + resultSeasonSelection.setAdapter() resultRangeSelection.setAdapter() resultDubSelection.setAdapter() @@ -180,12 +239,97 @@ class ResultFragmentTv : ResultFragment() { EpisodeAdapter( false, { episodeClick -> - viewModel.handleAction(activity, episodeClick) + viewModel.handleAction(episodeClick) }, { downloadClickEvent -> DownloadButtonSetup.handleDownloadClick(downloadClickEvent) } ) + + resultCastItems.layoutManager = object : LinearListLayout(view.context) { + override fun onRequestChildFocus( + parent: RecyclerView, + state: RecyclerView.State, + child: View, + focused: View? + ): Boolean { + // Make the cast always focus the first visible item when focused + // from somewhere else. Otherwise it jumps to the last item. + return if (parent.focusedChild == null) { + scrollToPosition(this.findFirstCompletelyVisibleItemPosition()) + true + } else { + super.onRequestChildFocus(parent, state, child, focused) + } + } + }.apply { + this.orientation = RecyclerView.HORIZONTAL + } + resultCastItems.adapter = ActorAdaptor() + } + + observeNullable(viewModel.resumeWatching) { resume -> + binding?.apply { + if (resume == null) { + resultResumeParent.isVisible = false + return@observeNullable + } + resultResumeParent.isVisible = true + resume.progress?.let { progress -> + resultResumeSeriesTitle.apply { + isVisible = !resume.isMovie + text = + if (resume.isMovie) null else context?.getNameFull( + resume.result.name, + resume.result.episode, + resume.result.season + ) + } + + resultResumeSeriesProgressText.setText(progress.progressLeft) + resultResumeSeriesProgress.apply { + isVisible = true + this.max = progress.maxProgress + this.progress = progress.progress + } + resultResumeProgressHolder.isVisible = true + } ?: run { + resultResumeProgressHolder.isVisible = false + resultResumeSeriesProgress.isVisible = false + resultResumeSeriesTitle.isVisible = false + resultResumeSeriesProgressText.isVisible = false + } + + resultResumeSeriesButton.isVisible = !resume.isMovie + resultResumeSeriesButton.setOnClickListener { + viewModel.handleAction( + EpisodeClickEvent( + storedData.playerAction, //?: ACTION_PLAY_EPISODE_IN_PLAYER, + resume.result + ) + ) + } + } + } + + observe(viewModel.trailers) { trailersLinks -> + context?.updateHasTrailers() + if (!LoadResponse.isTrailersEnabled) return@observe + val trailers = trailersLinks.flatMap { it.mirros } + binding?.resultPlayTrailer?.apply { + isGone = trailers.isEmpty() + setOnClickListener { + if (trailers.isEmpty()) return@setOnClickListener + activity.navigate( + R.id.global_to_navigation_player, GeneratorPlayer.newInstance( + ExtractorLinkGenerator( + trailers, + emptyList() + ) + ) + ) + } + } } observe(viewModel.watchStatus) { watchType -> @@ -211,13 +355,11 @@ class ResultFragmentTv : ResultFragment() { resultPlayMovie.setText(text) resultPlayMovie.setOnClickListener { viewModel.handleAction( - activity, EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep) ) } resultPlayMovie.setOnLongClickListener { viewModel.handleAction( - activity, EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep) ) return@setOnLongClickListener true @@ -397,8 +539,7 @@ class ResultFragmentTv : ResultFragment() { is Resource.Failure -> { resultErrorText.text = - (this@ResultFragmentTv.context?.let { getStoredData(it) }?.url?.plus("\n") - ?: "") + data.errorString + storedData.url.plus("\n") + data.errorString } } @@ -407,7 +548,7 @@ class ResultFragmentTv : ResultFragment() { resultLoading.isVisible = data is Resource.Loading resultLoadingError.isVisible = data is Resource.Failure - resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure + //resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure } } } 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 3de6edd2..6bf5ac47 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 @@ -19,6 +19,7 @@ import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer @@ -1144,9 +1145,9 @@ class ResultViewModel2 : ViewModel() { } - fun handleAction(activity: Activity?, click: EpisodeClickEvent) = + fun handleAction(click: EpisodeClickEvent) = viewModelScope.launchSafe { - handleEpisodeClickEvent(activity, click) + handleEpisodeClickEvent(click) } data class ExternalApp( @@ -1176,7 +1177,7 @@ class ResultViewModel2 : ViewModel() { _episodeSynopsis.postValue(null) } - private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) { + private suspend fun handleEpisodeClickEvent(click: EpisodeClickEvent) { when (click.action) { ACTION_SHOW_OPTIONS -> { val options = mutableListOf>() @@ -1234,7 +1235,6 @@ class ResultViewModel2 : ViewModel() { options ) { result -> handleEpisodeClickEvent( - activity, click.copy(action = result ?: return@postPopup) ) } @@ -1244,13 +1244,11 @@ class ResultViewModel2 : ViewModel() { activity?.let { ctx -> if (ctx.isConnectedToChromecast()) { handleEpisodeClickEvent( - activity, click.copy(action = ACTION_CHROME_CAST_EPISODE) ) } else { val action = getPlayerAction(ctx) handleEpisodeClickEvent( - activity, click.copy(action = action) ) } @@ -2210,7 +2208,6 @@ class ResultViewModel2 : ViewModel() { for (ep in currentRange) { if (ep.getWatchProgress() > 0.9) continue handleAction( - activity, EpisodeClickEvent( getPlayerAction(activity), ep @@ -2231,7 +2228,6 @@ class ResultViewModel2 : ViewModel() { } ?: return@launchSafe handleAction( - activity, EpisodeClickEvent( getPlayerAction(activity), episode diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index 9678b04f..be99b536 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -33,6 +33,7 @@ import androidx.core.widget.ContentLoadingProgressBar import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.navigation.fragment.findNavController +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -54,6 +55,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.plugins.RepositoryManager import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching +import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.ui.WebviewFragment import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings @@ -597,6 +599,14 @@ object AppUtils { startAction: Int = 0, startValue: Int = 0 ) { + try { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + Kitsu.isEnabled = + settingsManager.getBoolean(this.getString(R.string.show_kitsu_posters_key), true) + }catch (t : Throwable) { + logError(t) + } + this.runOnUiThread { // viewModelStore.clear() this.navigate( diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 0fc3d2e9..64e2da17 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -88,6 +88,7 @@ android:text="@string/reload_error" app:icon="@drawable/ic_baseline_autorenew_24" /> + @@ -457,7 +459,7 @@ android:minWidth="250dp" android:nextFocusRight="@id/download_button" android:nextFocusUp="@id/result_cast_items" - android:nextFocusDown="@id/result_resume_series_button_play" + android:nextFocusDown="@id/result_resume_series_button" android:text="@string/play_trailer_button" android:visibility="gone" app:icon="@drawable/ic_baseline_play_arrow_24"> @@ -486,7 +488,7 @@ android:minWidth="250dp" android:nextFocusLeft="@id/download_button" android:nextFocusRight="@id/result_bookmark_button" - android:nextFocusDown="@id/result_resume_series_button_play" + android:nextFocusDown="@id/result_resume_series_button" android:text="@string/type_none" android:visibility="visible" /> @@ -521,7 +523,7 @@ android:orientation="horizontal">