result -> result specific

This commit is contained in:
LagradOst 2023-07-18 22:18:14 +02:00
parent d5c42f7d5a
commit 03d50a943a
14 changed files with 704 additions and 680 deletions

View file

@ -223,7 +223,7 @@ dependencies {
//implementation("com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT")
// newpipe yt taken from https://github.com/TeamNewPipe/NewPipe/blob/dev/app/build.gradle#L204
implementation("com.github.TeamNewPipe:NewPipeExtractor:master-SNAPSHOT")
implementation("com.github.TeamNewPipe:NewPipeExtractor:8495ad619e")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
// Library/extensions searching with Levenshtein distance

View file

@ -361,14 +361,11 @@ class HomeFragment : Fragment() {
?.toMutableList()
?: mutableListOf(TvType.Movie, TvType.TvSeries)
val cancelBtt = dialog.findViewById<MaterialButton>(R.id.cancel_btt)
val applyBtt = dialog.findViewById<MaterialButton>(R.id.apply_btt)
cancelBtt?.setOnClickListener {
binding.cancelBtt.setOnClickListener {
dialog.dismissSafe()
}
applyBtt?.setOnClickListener {
binding.applyBtt.setOnClickListener {
if (currentApiName != selectedApiName) {
currentApiName?.let(callback)
}
@ -493,15 +490,54 @@ class HomeFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
fixGrid()
binding?.homeChangeApiLoading?.setOnClickListener(apiChangeClickListener)
binding?.homeChangeApiLoading?.setOnClickListener(apiChangeClickListener)
binding?.homeApiFab?.setOnClickListener(apiChangeClickListener)
binding?.homeRandom?.setOnClickListener {
if (listHomepageItems.isNotEmpty()) {
activity.loadSearchResult(listHomepageItems.random())
binding?.apply {
homeChangeApiLoading.setOnClickListener(apiChangeClickListener)
//homeChangeApiLoading.setOnClickListener(apiChangeClickListener)
homeApiFab.setOnClickListener(apiChangeClickListener)
homeRandom.setOnClickListener {
if (listHomepageItems.isNotEmpty()) {
activity.loadSearchResult(listHomepageItems.random())
}
}
homeMasterRecycler.adapter =
HomeParentItemAdapterPreview(
mutableListOf(),
homeViewModel
)
fixPaddingStatusbar(homeLoadingStatusbar)
if (isTvSettings()) {
homeApiFab.isVisible = false
if (isTrueTvSettings()) {
homeChangeApiLoading.isVisible = true
homeChangeApiLoading.isFocusable = true
homeChangeApiLoading.isFocusableInTouchMode = true
}
// home_bookmark_select?.isFocusable = true
// home_bookmark_select?.isFocusableInTouchMode = true
} else {
homeApiFab.isVisible = true
homeChangeApiLoading.isVisible = false
}
homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) { //check for scroll down
homeApiFab.shrink() // hide
homeRandom.shrink()
} else if (dy < -5) {
if (!isTvSettings()) {
homeApiFab.extend() // show
homeRandom.extend()
}
}
super.onScrolled(recyclerView, dx, dy)
}
})
}
//Load value for toggling Random button. Hide at startup
context?.let {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
@ -551,13 +587,9 @@ class HomeFragment : Fragment() {
}
is Resource.Failure -> {
homeLoadingShimmer.stopShimmer()
resultErrorText.text = data.errorString
homeReloadConnectionerror.setOnClickListener(apiChangeClickListener)
homeReloadConnectionOpenInBrowser.setOnClickListener { view ->
val validAPIs = apis//.filter { api -> api.hasMainPage }
@ -598,7 +630,6 @@ class HomeFragment : Fragment() {
//context?.fixPaddingStatusbarView(home_statusbar)
//context?.fixPaddingStatusbar(home_padding)
fixPaddingStatusbar(binding?.homeLoadingStatusbar)
observeNullable(homeViewModel.popup) { item ->
if (item == null) {
@ -620,52 +651,13 @@ class HomeFragment : Fragment() {
})
}
binding?.homeMasterRecycler?.adapter =
HomeParentItemAdapterPreview(
mutableListOf(),
homeViewModel
)
homeViewModel.reloadStored()
//loadHomePage(false)
binding?.homeMasterRecycler?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
binding?.apply {
if (dy > 0) { //check for scroll down
homeApiFab.shrink() // hide
homeRandom.shrink()
} else if (dy < -5) {
if (!isTvSettings()) {
homeApiFab.extend() // show
homeRandom.extend()
}
}
}
super.onScrolled(recyclerView, dx, dy)
}
})
// nice profile pic on homepage
//home_profile_picture_holder?.isVisible = false
// just in case
binding?.apply {
if (isTvSettings()) {
homeApiFab.isVisible = false
if (isTrueTvSettings()) {
homeChangeApiLoading.isVisible = true
homeChangeApiLoading.isFocusable = true
homeChangeApiLoading.isFocusableInTouchMode = true
}
// home_bookmark_select?.isFocusable = true
// home_bookmark_select?.isFocusableInTouchMode = true
} else {
homeApiFab.isVisible = true
homeChangeApiLoading.isVisible = false
}
}
//TODO READD THIS
/*for (syncApi in OAuth2Apis) {
val login = syncApi.loginInfo()

View file

@ -401,7 +401,7 @@ class HomeParentItemAdapterPreview(
observe(viewModel.bookmarks) {
updateBookmarks(it)
}
observe(viewModel.availableWatchStatusTypes) { (visible, checked) ->
observe(viewModel.availableWatchStatusTypes) { (checked, visible) ->
for ((chip, watch) in toggleList) {
chip.apply {
isVisible = visible.contains(watch)

View file

@ -74,15 +74,15 @@ abstract class AbstractPlayerFragment(
var isBuffering = true
protected open var hasPipModeSupport = true
lateinit var playerPausePlayHolderHolder : FrameLayout
lateinit var playerPausePlay : ImageView
lateinit var playerBuffering : ProgressBar
var playerPausePlayHolderHolder : FrameLayout? = null
var playerPausePlay : ImageView? = null
var playerBuffering : ProgressBar? = null
var playerView : PlayerView? = null
var piphide : FrameLayout? = null
var subtitleHolder : FrameLayout? = null
@LayoutRes
protected var layout: Int = R.layout.fragment_player
protected open var layout: Int = R.layout.fragment_player
open fun nextEpisode() {
throw NotImplementedError()
@ -141,15 +141,15 @@ abstract class AbstractPlayerFragment(
isBuffering = CSPlayerLoading.IsBuffering == isPlaying
if (isBuffering) {
playerPausePlayHolderHolder.isVisible = false
playerBuffering.isVisible = true
playerPausePlayHolderHolder?.isVisible = false
playerBuffering?.isVisible = true
} else {
playerPausePlayHolderHolder.isVisible = true
playerBuffering.isVisible = false
playerPausePlayHolderHolder?.isVisible = true
playerBuffering?.isVisible = false
if (wasPlaying != isPlaying) {
playerPausePlay.setImageResource(if (isPlayingRightNow) R.drawable.play_to_pause else R.drawable.pause_to_play)
val drawable = playerPausePlay.drawable
playerPausePlay?.setImageResource(if (isPlayingRightNow) R.drawable.play_to_pause else R.drawable.pause_to_play)
val drawable = playerPausePlay?.drawable
var startedAnimation = false
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
@ -171,10 +171,10 @@ abstract class AbstractPlayerFragment(
// somehow the phone is wacked
if (!startedAnimation) {
playerPausePlay.setImageResource(if (isPlayingRightNow) R.drawable.netflix_pause else R.drawable.netflix_play)
playerPausePlay?.setImageResource(if (isPlayingRightNow) R.drawable.netflix_pause else R.drawable.netflix_play)
}
} else {
playerPausePlay.setImageResource(if (isPlayingRightNow) R.drawable.netflix_pause else R.drawable.netflix_play)
playerPausePlay?.setImageResource(if (isPlayingRightNow) R.drawable.netflix_pause else R.drawable.netflix_play)
}
}

View file

@ -10,7 +10,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
@ -22,18 +21,16 @@ 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.CommonActivity.showToast
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.services.SubscriptionWorkManager
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.quicksearch.QuickSearchFragment
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
@ -47,7 +44,6 @@ 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.fixPaddingStatusbar
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
@ -89,10 +85,8 @@ 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_swipe.*
import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.result_sync.*
import kotlinx.android.synthetic.main.trailer_custom_layout.*
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
@ -194,7 +188,7 @@ fun ResultEpisode.getWatchProgress(): Float {
return (getDisplayPosition() / 1000).toFloat() / (duration / 1000).toFloat()
}
open class ResultFragment : ResultTrailerPlayer() {
open class ResultFragment : FullScreenPlayer() {
companion object {
const val URL_BUNDLE = "url"
const val API_NAME_BUNDLE = "apiName"
@ -251,7 +245,9 @@ open class ResultFragment : ResultTrailerPlayer() {
protected lateinit var viewModel: ResultViewModel2 //by activityViewModels()
protected lateinit var syncModel: SyncViewModel
protected open val resultLayout = R.layout.fragment_result_swipe
//protected open val resultLayout = R.layout.fragment_result_swipe
override var layout = R.layout.fragment_result_swipe
override fun onCreateView(
inflater: LayoutInflater,
@ -263,7 +259,8 @@ open class ResultFragment : ResultTrailerPlayer() {
syncModel =
ViewModelProvider(this)[SyncViewModel::class.java]
return inflater.inflate(resultLayout, container, false)
return super.onCreateView(inflater, container, savedInstanceState)
//return inflater.inflate(resultLayout, container, false)
}
override fun onDestroyView() {
@ -285,167 +282,12 @@ open class ResultFragment : ResultTrailerPlayer() {
super.onDestroy()
}
/// 0 = LOADING, 1 = ERROR LOADING, 2 = LOADED
private fun updateVisStatus(state: Int) {
when (state) {
0 -> {
result_bookmark_fab?.isGone = true
result_loading?.isVisible = true
result_finish_loading?.isVisible = false
result_loading_error?.isVisible = false
}
1 -> {
result_bookmark_fab?.isGone = true
result_loading?.isVisible = false
result_finish_loading?.isVisible = false
result_loading_error?.isVisible = true
result_reload_connection_open_in_browser?.isVisible = true
}
2 -> {
result_bookmark_fab?.isGone = isTrueTvSettings()
result_bookmark_fab?.extend()
//if (result_bookmark_button?.context?.isTrueTvSettings() == true) {
// when {
// result_play_movie?.isVisible == true -> {
// result_play_movie?.requestFocus()
// }
// result_resume_series_button?.isVisible == true -> {
// result_resume_series_button?.requestFocus()
// }
// else -> {
// result_bookmark_button?.requestFocus()
// }
// }
//}
result_loading?.isVisible = false
result_finish_loading?.isVisible = true
result_loading_error?.isVisible = false
}
}
}
open fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
}
private fun updateUI() {
syncModel.updateUserData()
viewModel.reloadEpisodes()
}
open fun updateMovie(data: Resource<Pair<UiText, ResultEpisode>>?) {
when (data) {
is Resource.Success -> {
data.value.let { (text, ep) ->
result_play_movie.setText(text)
result_play_movie?.setOnClickListener {
viewModel.handleAction(
activity,
EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep)
)
}
result_play_movie?.setOnLongClickListener {
viewModel.handleAction(
activity,
EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep)
)
return@setOnLongClickListener true
}
val show =
viewModel.currentRepo?.api?.hasDownloadSupport == true && !isTvSettings()
if (show) {
download_button?.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached(
ep.name,
ep.poster,
0,
null,
ep.id,
ep.id,
null,
null,
System.currentTimeMillis(),
)
) { click ->
when (click.action) {
DOWNLOAD_ACTION_DOWNLOAD -> {
viewModel.handleAction(
activity,
EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, ep)
)
}
else -> handleDownloadClick(click)
}
}
}
download_button?.isVisible = show
}
}
else -> {
download_button?.isVisible = false
result_play_movie?.isVisible = false
}
}
}
open fun updateEpisodes(episodes: Resource<List<ResultEpisode>>?) {
when (episodes) {
is Resource.Loading -> {
result_episode_loading?.isVisible = true
result_episodes?.isVisible = false
}
is Resource.Success -> {
result_episodes?.isVisible = true
result_episode_loading?.isVisible = false
/*
* Okay so what is this fuckery?
* Basically Android TV will crash if you request a new focus while
* the adapter gets updated.
*
* This means that if you load thumbnails and request a next focus at the same time
* the app will crash without any way to catch it!
*
* How to bypass this?
* This code basically steals the focus for 500ms and puts it in an inescapable view
* then lets out the focus by requesting focus to result_episodes
*/
// Do not use this.isTv, that is the player
val isTv = isTvSettings()
val hasEpisodes =
!(result_episodes?.adapter as? EpisodeAdapter?)?.cardList.isNullOrEmpty()
if (isTv && hasEpisodes) {
// Make it impossible to focus anywhere else!
temporary_no_focus?.isFocusable = true
temporary_no_focus?.requestFocus()
}
(result_episodes?.adapter as? EpisodeAdapter)?.updateList(episodes.value)
if (isTv && hasEpisodes) main {
delay(500)
temporary_no_focus?.isFocusable = false
// This might make some people sad as it changes the focus when leaving an episode :(
result_episodes?.requestFocus()
}
}
else -> {
result_episode_loading?.isVisible = false
result_episodes?.isVisible = false
}
}
}
data class StoredData(
val url: String?,
val apiName: String,
@ -455,7 +297,7 @@ open class ResultFragment : ResultTrailerPlayer() {
val playerAction: Int
)
private fun getStoredData(context: Context): StoredData? {
fun getStoredData(context: Context): StoredData? {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
val url = arguments?.getString(URL_BUNDLE)
val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return null
@ -537,7 +379,6 @@ open class ResultFragment : ResultTrailerPlayer() {
context?.updateHasTrailers()
activity?.loadCache()
fixPaddingStatusbar(result_top_bar)
//activity?.fixPaddingStatusbar(result_barstatus)
/* val backParameter = result_back.layoutParams as FrameLayout.LayoutParams
@ -554,35 +395,6 @@ open class ResultFragment : ResultTrailerPlayer() {
val storedData = (activity ?: context)?.let {
getStoredData(it)
}
syncModel.addFromUrl(storedData?.url)
val api = getApiFromNameNull(storedData?.apiName)
result_episodes?.adapter =
EpisodeAdapter(
api?.hasDownloadSupport == true && !isTvSettings(),
{ episodeClick ->
viewModel.handleAction(activity, episodeClick)
},
{ downloadClickEvent ->
handleDownloadClick(downloadClickEvent)
}
)
observe(viewModel.episodeSynopsis) { description ->
view.context?.let { ctx ->
val builder: AlertDialog.Builder =
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
builder.setMessage(description.html())
.setTitle(R.string.synopsis)
.setOnDismissListener {
viewModel.releaseEpisodeSynopsis()
}
.show()
}
}
// This is to band-aid FireTV navigation
val isTv = isTvSettings()
@ -638,215 +450,6 @@ open class ResultFragment : ResultTrailerPlayer() {
result_resume_series_button_play?.setOnClickListener(click)
}
observeNullable(viewModel.episodes) { episodes ->
updateEpisodes(episodes)
}
result_cast_items?.setOnFocusChangeListener { _, hasFocus ->
// Always escape focus
if (hasFocus) result_bookmark_button?.requestFocus()
}
observe(viewModel.trailers) { trailers ->
setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet!
}
observe(viewModel.recommendations) { recommendations ->
setRecommendations(recommendations, null)
}
observeNullable(viewModel.movie) { data ->
updateMovie(data)
}
observe(viewModel.page) { data ->
if (data == null) return@observe
when (data) {
is Resource.Success -> {
val d = data.value
updateVisStatus(2)
result_vpn.setText(d.vpnText)
result_info.setText(d.metaText)
result_no_episodes.setText(d.noEpisodesFoundText)
result_title.setText(d.titleText)
result_meta_site.setText(d.apiName)
result_meta_type.setText(d.typeText)
result_meta_year.setText(d.yearText)
result_meta_duration.setText(d.durationText)
result_meta_rating.setText(d.ratingText)
result_cast_text.setText(d.actorsText)
result_next_airing.setText(d.nextAiringEpisode)
result_next_airing_time.setText(d.nextAiringDate)
result_poster.setImage(d.posterImage)
result_poster_background.setImage(d.posterBackgroundImage)
//result_trailer_thumbnail.setImage(d.posterBackgroundImage, fadeIn = false)
if (d.posterImage != null && !isTrueTvSettings())
result_poster_holder?.setOnClickListener {
try {
context?.let { ctx ->
runBlocking {
val sourceBuilder = AlertDialog.Builder(ctx)
sourceBuilder.setView(R.layout.result_poster)
val sourceDialog = sourceBuilder.create()
sourceDialog.show()
sourceDialog.findViewById<ImageView?>(R.id.imgPoster)
?.apply {
setImage(d.posterImage)
setOnClickListener {
sourceDialog.dismissSafe()
}
}
}
}
} catch (e: Exception) {
logError(e)
}
}
result_cast_items?.isVisible = d.actors != null
(result_cast_items?.adapter as? ActorAdaptor)?.apply {
updateList(d.actors ?: emptyList())
}
observeNullable(viewModel.subscribeStatus) { isSubscribed ->
result_subscribe?.isVisible = isSubscribed != null
if (isSubscribed == null) return@observeNullable
val drawable = if (isSubscribed) {
R.drawable.ic_baseline_notifications_active_24
} else {
R.drawable.baseline_notifications_none_24
}
result_subscribe?.setImageResource(drawable)
}
result_subscribe?.setOnClickListener {
val isSubscribed =
viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener
val message = if (isSubscribed) {
// Kinda icky to have this here, but it works.
SubscriptionWorkManager.enqueuePeriodicWork(context)
R.string.subscription_new
} else {
R.string.subscription_deleted
}
val name = (viewModel.page.value as? Resource.Success)?.value?.title
?: txt(R.string.no_data).asStringNull(context) ?: ""
showToast(txt(message, name), Toast.LENGTH_SHORT)
}
result_open_in_browser?.isVisible = d.url.startsWith("http")
result_open_in_browser?.setOnClickListener {
val i = Intent(ACTION_VIEW)
i.data = Uri.parse(d.url)
try {
startActivity(i)
} catch (e: Exception) {
logError(e)
}
}
result_search?.setOnClickListener {
QuickSearchFragment.pushSearch(activity, d.title)
}
result_share?.setOnClickListener {
try {
val i = Intent(ACTION_SEND)
i.type = "text/plain"
i.putExtra(EXTRA_SUBJECT, d.title)
i.putExtra(EXTRA_TEXT, d.url)
startActivity(createChooser(i, d.title))
} catch (e: Exception) {
logError(e)
}
}
if (syncModel.addSyncs(d.syncData)) {
syncModel.updateMetaAndUser()
syncModel.updateSynced()
} else {
syncModel.addFromUrl(d.url)
}
result_description.setTextHtml(d.plotText)
if (this !is ResultFragmentTv) // dont want this clickable on tv layout
result_description?.setOnClickListener { view ->
view.context?.let { ctx ->
val builder: AlertDialog.Builder =
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
builder.setMessage(d.plotText.asString(ctx).html())
.setTitle(d.plotHeaderText.asString(ctx))
.show()
}
}
d.comingSoon.let { soon ->
result_coming_soon?.isVisible = soon
result_data_holder?.isGone = soon
}
val tags = d.tags
result_tag_holder?.isVisible = tags.isNotEmpty()
result_tag?.apply {
removeAllViews()
tags.forEach { tag ->
val chip = Chip(context)
val chipDrawable = ChipDrawable.createFromAttributes(
context,
null,
0,
R.style.ChipFilled
)
chip.setChipDrawable(chipDrawable)
chip.text = tag
chip.isChecked = false
chip.isCheckable = false
chip.isFocusable = false
chip.isClickable = false
chip.setTextColor(context.colorFromAttribute(R.attr.textColor))
addView(chip)
}
}
// if (tags.isNotEmpty()) {
//result_tag_holder?.visibility = VISIBLE
//val isOnTv = isTrueTvSettings()
/*for ((index, tag) in tags.withIndex()) {
val viewBtt = layoutInflater.inflate(R.layout.result_tag, null)
val btt = viewBtt.findViewById<MaterialButton>(R.id.result_tag_card)
btt.text = tag
btt.isFocusable = !isOnTv
btt.isClickable = !isOnTv
result_tag?.addView(viewBtt, index)
}*/
//}
}
is Resource.Failure -> {
result_error_text.text = storedData?.url?.plus("\n") + data.errorString
updateVisStatus(1)
}
is Resource.Loading -> {
updateVisStatus(0)
}
}
}
context?.let { ctx ->
//result_bookmark_button?.isVisible = ctx.isTvSettings()
@ -879,17 +482,6 @@ open class ResultFragment : ResultTrailerPlayer() {
}
}
result_open_in_browser?.isVisible = storedData.url.startsWith("http")
result_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 {

View file

@ -1,8 +1,11 @@
package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
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
@ -15,6 +18,7 @@ import android.view.animation.DecelerateInterpolator
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
@ -41,12 +45,19 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
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.quicksearch.QuickSearchFragment
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
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
@ -55,14 +66,25 @@ 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.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.populateChips
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import kotlinx.android.synthetic.main.download_button.result_download_movie
import kotlinx.android.synthetic.main.fragment_result.download_button
import kotlinx.android.synthetic.main.fragment_result.result_episode_loading
import kotlinx.android.synthetic.main.fragment_result.result_episodes
import kotlinx.android.synthetic.main.fragment_result.result_play_movie
import kotlinx.android.synthetic.main.fragment_result_tv.temporary_no_focus
import kotlinx.coroutines.delay
class ResultFragmentPhone : ResultFragment() {
private var binding: FragmentResultSwipeBinding? = null
private var resultBinding: FragmentResultBinding? = null
private var recommendationBinding: ResultRecommendationsBinding? = null
private var syncBinding: ResultSyncBinding? = null
open class ResultFragmentPhone : ResultFragment(),
PanelsChildGestureRegionObserver.GestureRegionsListener {
protected var binding: FragmentResultSwipeBinding? = null
protected var resultBinding: FragmentResultBinding? = null
protected var recommendationBinding: ResultRecommendationsBinding? = null
protected var syncBinding: ResultSyncBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -205,43 +227,158 @@ class ResultFragmentPhone : ResultFragment() {
var selectSeason: String? = null
private fun setUrl(url : String?) {
if(url == null) {
binding?.resultOpenInBrowser?.isVisible = false
return
}
binding?.resultOpenInBrowser?.apply {
isVisible = url.startsWith("http")
setOnClickListener {
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
try {
startActivity(i)
} catch (e: Exception) {
logError(e)
}
}
}
}
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return
super.onViewCreated(view, savedInstanceState)
UIHelper.fixPaddingStatusbar(binding?.resultTopBar)
val storedData = (activity ?: context)?.let {
getStoredData(it)
}
setUrl(storedData?.url)
syncModel.addFromUrl(storedData?.url)
playerBinding?.playerOpenSource?.setOnClickListener {
currentTrailers.getOrNull(currentTrailerIndex)?.let {
context?.openBrowser(it.url)
val api = APIHolder.getApiFromNameNull(apiName)
resultBinding?.apply {
resultEpisodes.adapter =
EpisodeAdapter(
api?.hasDownloadSupport == true,
{ episodeClick ->
viewModel.handleAction(activity, episodeClick)
},
{ downloadClickEvent ->
DownloadButtonSetup.handleDownloadClick(downloadClickEvent)
}
)
resultCastItems.let {
PanelsChildGestureRegionObserver.Provider.get().register(it)
}
resultScroll.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY
if (dy > 0) { //check for scroll down
binding?.resultBookmarkFab?.shrink()
} else if (dy < -5) {
binding?.resultBookmarkFab?.extend()
}
if (!isFullScreenPlayer && player.getIsPlaying()) {
if (scrollY > (resultBinding?.fragmentTrailer?.playerBackground?.height
?: scrollY)
) {
player.handleEvent(CSPlayerEvent.Pause)
}
}
})
}
binding?.apply {
resultOverlappingPanels.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
resultOverlappingPanels.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
resultBack.setOnClickListener {
activity?.popCurrentPage()
}
resultMiniSync.adapter = ImageAdapter(
nextFocusDown = R.id.result_sync_set_score,
clickCallback = { action ->
if (action == IMAGE_CLICK || action == IMAGE_LONG_CLICK) {
if (binding?.resultOverlappingPanels?.getSelectedPanel()?.ordinal == 1) {
binding?.resultOverlappingPanels?.openStartPanel()
} else {
binding?.resultOverlappingPanels?.closePanels()
}
}
})
resultSubscribe.setOnClickListener {
val isSubscribed =
viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener
val message = if (isSubscribed) {
// Kinda icky to have this here, but it works.
SubscriptionWorkManager.enqueuePeriodicWork(context)
R.string.subscription_new
} else {
R.string.subscription_deleted
}
val name = (viewModel.page.value as? Resource.Success)?.value?.title
?: txt(R.string.no_data).asStringNull(context) ?: ""
CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT)
}
mediaRouteButton.apply {
val chromecastSupport = api?.hasChromecastSupport == true
alpha = if (chromecastSupport) 1f else 0.3f
if (!chromecastSupport) {
setOnClickListener {
CommonActivity.showToast(
R.string.no_chromecast_support_toast,
Toast.LENGTH_LONG
)
}
}
activity?.let { act ->
if (act.isCastApiAvailable()) {
try {
CastButtonFactory.setUpMediaRouteButton(act, this)
val castContext = CastContext.getSharedInstance(act.applicationContext)
isGone = castContext.castState == CastState.NO_DEVICES_AVAILABLE
// this shit leaks for some reason
//castContext.addCastStateListener { state ->
// media_route_button?.isGone = state == CastState.NO_DEVICES_AVAILABLE
//}
} catch (e: Exception) {
logError(e)
}
}
}
}
}
binding?.resultOverlappingPanels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
binding?.resultOverlappingPanels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
recommendationBinding?.resultRecommendationsList?.apply {
spanCount = 3
adapter =
SearchAdapter(
ArrayList(),
this,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
playerBinding?.apply {
playerOpenSource.setOnClickListener {
currentTrailers.getOrNull(currentTrailerIndex)?.let {
context?.openBrowser(it.url)
}
}
}
recommendationBinding?.apply {
resultRecommendationsList.apply {
spanCount = 3
adapter =
SearchAdapter(
ArrayList(),
this,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
}
}
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
resultBinding?.resultCastItems?.let {
PanelsChildGestureRegionObserver.Provider.get().register(it)
}
binding?.resultBack?.setOnClickListener {
activity?.popCurrentPage()
}
/*
result_bookmark_button?.setOnClickListener {
it.popupMenuNoIcons(
@ -253,62 +390,165 @@ class ResultFragmentPhone : ResultFragment() {
}
}*/
binding?.resultMiniSync?.adapter = ImageAdapter(
nextFocusDown = R.id.result_sync_set_score,
clickCallback = { action ->
if (action == IMAGE_CLICK || action == IMAGE_LONG_CLICK) {
if (binding?.resultOverlappingPanels?.getSelectedPanel()?.ordinal == 1) {
binding?.resultOverlappingPanels?.openStartPanel()
observeNullable(viewModel.subscribeStatus) { isSubscribed ->
binding?.resultSubscribe?.isVisible = isSubscribed != null
if (isSubscribed == null) return@observeNullable
val drawable = if (isSubscribed) {
R.drawable.ic_baseline_notifications_active_24
} else {
R.drawable.baseline_notifications_none_24
}
binding?.resultSubscribe?.setImageResource(drawable)
}
observe(viewModel.trailers) { trailers ->
setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet!
}
observeNullable(viewModel.episodes) { episodes ->
resultBinding?.apply {
// no failure?
resultEpisodeLoading.isVisible = episodes is Resource.Loading
resultEpisodes.isVisible = episodes is Resource.Success
if(episodes is Resource.Success) {
(resultEpisodes.adapter as? EpisodeAdapter)?.updateList(episodes.value)
}
}
}
observeNullable(viewModel.movie) { data ->
resultBinding?.apply {
resultPlayMovie.isVisible = data is Resource.Success
downloadButton.isVisible =
data is Resource.Success && viewModel.currentRepo?.api?.hasDownloadSupport == true
(data as? Resource.Success)?.value?.let { (text, ep) ->
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
}
downloadButton.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached(
ep.name,
ep.poster,
0,
null,
ep.id,
ep.id,
null,
null,
System.currentTimeMillis(),
)
) { click ->
when (click.action) {
DOWNLOAD_ACTION_DOWNLOAD -> {
viewModel.handleAction(
activity,
EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, ep)
)
}
else -> DownloadButtonSetup.handleDownloadClick(click)
}
}
}
}
}
observe(viewModel.page) { data ->
if (data == null) return@observe
resultBinding?.apply {
(data as? Resource.Success)?.value?.let { d ->
resultVpn.setText(d.vpnText)
resultInfo.setText(d.metaText)
resultNoEpisodes.setText(d.noEpisodesFoundText)
resultTitle.setText(d.titleText)
resultMetaSite.setText(d.apiName)
resultMetaType.setText(d.typeText)
resultMetaYear.setText(d.yearText)
resultMetaDuration.setText(d.durationText)
resultMetaRating.setText(d.ratingText)
resultCastText.setText(d.actorsText)
resultNextAiring.setText(d.nextAiringEpisode)
resultNextAiringTime.setText(d.nextAiringDate)
resultPoster.setImage(d.posterImage)
resultPosterBackground.setImage(d.posterBackgroundImage)
resultDescription.setTextHtml(d.plotText)
resultDescription.setOnClickListener { view ->
// todo bottom view?
view.context?.let { ctx ->
val builder: AlertDialog.Builder =
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
builder.setMessage(d.plotText.asString(ctx).html())
.setTitle(d.plotHeaderText.asString(ctx))
.show()
}
}
populateChips(resultTag, d.tags)
resultComingSoon.isVisible = d.comingSoon
resultDataHolder.isGone = d.comingSoon
resultCastItems.isGone = d.actors.isNullOrEmpty()
(resultCastItems.adapter as? ActorAdaptor)?.updateList(d.actors ?: emptyList())
if (syncModel.addSyncs(d.syncData)) {
syncModel.updateMetaAndUser()
syncModel.updateSynced()
} else {
binding?.resultOverlappingPanels?.closePanels()
syncModel.addFromUrl(d.url)
}
binding?.apply {
resultSearch.setOnClickListener {
QuickSearchFragment.pushSearch(activity, d.title)
}
resultShare.setOnClickListener {
try {
val i = Intent(Intent.ACTION_SEND)
i.type = "text/plain"
i.putExtra(Intent.EXTRA_SUBJECT, d.title)
i.putExtra(Intent.EXTRA_TEXT, d.url)
startActivity(Intent.createChooser(i, d.title))
} catch (e: Exception) {
logError(e)
}
}
setUrl(d.url)
resultBookmarkFab.apply {
isVisible = true
extend()
}
}
}
})
(data as? Resource.Failure)?.let { data ->
resultErrorText.text = (storedData?.url?.plus("\n") ?: "") + data.errorString
}
resultBinding?.resultScroll?.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY
if (dy > 0) { //check for scroll down
binding?.resultBookmarkFab?.shrink()
} else if (dy < -5) {
binding?.resultBookmarkFab?.extend()
}
if (!isFullScreenPlayer && player.getIsPlaying()) {
if (scrollY > (resultBinding?.fragmentTrailer?.playerBackground?.height
?: scrollY)
) {
player.handleEvent(CSPlayerEvent.Pause)
}
}
//result_poster_blur_holder?.translationY = -scrollY.toFloat()
})
val api = APIHolder.getApiFromNameNull(apiName)
binding?.resultBookmarkFab?.isVisible = data is Resource.Success
resultFinishLoading.isVisible = data is Resource.Success
binding?.mediaRouteButton?.apply {
val chromecastSupport = api?.hasChromecastSupport == true
alpha = if (chromecastSupport) 1f else 0.3f
if (!chromecastSupport) {
setOnClickListener {
CommonActivity.showToast(
R.string.no_chromecast_support_toast,
Toast.LENGTH_LONG
)
}
}
activity?.let { act ->
if (act.isCastApiAvailable()) {
try {
CastButtonFactory.setUpMediaRouteButton(act, this)
val castContext = CastContext.getSharedInstance(act.applicationContext)
isGone = castContext.castState == CastState.NO_DEVICES_AVAILABLE
// this shit leaks for some reason
//castContext.addCastStateListener { state ->
// media_route_button?.isGone = state == CastState.NO_DEVICES_AVAILABLE
//}
} catch (e: Exception) {
logError(e)
}
}
resultLoading.isVisible = data is Resource.Loading
resultLoadingError.isVisible = data is Resource.Failure
resultErrorText.isVisible = data is Resource.Failure
resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure
}
}
@ -350,6 +590,7 @@ class ResultFragmentPhone : ResultFragment() {
(binding?.resultMiniSync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon })
}
var currentSyncProgress = 0
fun setSyncMaxEpisodes(totalEpisodes: Int?) {
syncBinding?.resultSyncEpisodes?.max = (totalEpisodes ?: 0) * 1000
@ -439,7 +680,22 @@ class ResultFragmentPhone : ResultFragment() {
}
binding?.resultOverlappingPanels?.setStartPanelLockState(if (closed) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
}
observe(viewModel.recommendations) { recommendations ->
setRecommendations(recommendations, null)
}
observe(viewModel.episodeSynopsis) { description ->
// TODO bottom dialog
view.context?.let { ctx ->
val builder: AlertDialog.Builder =
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
builder.setMessage(description.html())
.setTitle(R.string.synopsis)
.setOnDismissListener {
viewModel.releaseEpisodeSynopsis()
}
.show()
}
}
context?.let { ctx ->
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
/*
@ -653,7 +909,7 @@ class ResultFragmentPhone : ResultFragment() {
binding?.resultOverlappingPanels?.setChildGestureRegions(gestureRegions)
}
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
private fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
val isInvalid = rec.isNullOrEmpty()
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName

View file

@ -1,11 +1,13 @@
package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
@ -20,19 +22,32 @@ import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
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.ExtractorLinkGenerator
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
import com.lagradost.cloudstream3.utils.AppUtils.html
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.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import kotlinx.android.synthetic.main.fragment_result.download_button
import kotlinx.android.synthetic.main.fragment_result.result_episodes
import kotlinx.android.synthetic.main.fragment_result.result_play_movie
import kotlinx.android.synthetic.main.fragment_result_tv.temporary_no_focus
import kotlinx.coroutines.delay
class ResultFragmentTv : ResultFragment() {
override val resultLayout = R.layout.fragment_result_tv
override var layout = R.layout.fragment_result_tv
private var binding: FragmentResultTvBinding? = null
@ -95,20 +110,6 @@ class ResultFragmentTv : ResultFragment() {
return focus == binding?.resultRoot
}
override fun updateEpisodes(episodes: Resource<List<ResultEpisode>>?) {
super.updateEpisodes(episodes)
if (episodes is Resource.Success && hasNoFocus()) {
binding?.resultEpisodes?.requestFocus()
}
}
override fun updateMovie(data: Resource<Pair<UiText, ResultEpisode>>?) {
super.updateMovie(data)
if (data is Resource.Success && hasNoFocus()) {
binding?.resultPlayMovie?.requestFocus()
}
}
override fun setTrailers(trailers: List<ExtractorLink>?) {
context?.updateHasTrailers()
if (!LoadResponse.isTrailersEnabled) return
@ -128,7 +129,7 @@ class ResultFragmentTv : ResultFragment() {
}
}
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
private fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
currentRecommendations = rec ?: emptyList()
val isInvalid = rec.isNullOrEmpty()
binding?.apply {
@ -151,6 +152,8 @@ class ResultFragmentTv : ResultFragment() {
var loadingDialog: Dialog? = null
var popupDialog: Dialog? = null
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
@ -163,6 +166,34 @@ class ResultFragmentTv : ResultFragment() {
resultRangeSelection.setAdapter()
resultDubSelection.setAdapter()
resultRecommendationsFilterSelection.setAdapter()
resultCastItems.setOnFocusChangeListener { _, hasFocus ->
// Always escape focus
if (hasFocus) binding?.resultBookmarkButton?.requestFocus()
}
resultBack.setOnClickListener {
activity?.popCurrentPage()
}
resultRecommendationsList.spanCount = 8
resultRecommendationsList.adapter =
SearchAdapter(
ArrayList(),
resultRecommendationsList,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
resultEpisodes.adapter =
EpisodeAdapter(
false,
{ episodeClick ->
viewModel.handleAction(activity, episodeClick)
},
{ downloadClickEvent ->
DownloadButtonSetup.handleDownloadClick(downloadClickEvent)
}
)
}
observe(viewModel.watchStatus) { watchType ->
@ -181,6 +212,30 @@ class ResultFragmentTv : ResultFragment() {
}
}
observeNullable(viewModel.movie) { data ->
binding?.apply {
resultPlayMovie.isVisible = data is Resource.Success
(data as? Resource.Success)?.value?.let { (text, ep) ->
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
}
if (hasNoFocus()) {
resultPlayMovie.requestFocus()
}
}
}
}
observeNullable(viewModel.selectPopup) { popup ->
if (popup == null) {
@ -257,21 +312,111 @@ class ResultFragmentTv : ResultFragment() {
observe(viewModel.seasonSelections) {
binding?.resultSeasonSelection.update(it)
}
binding?.apply {
resultBack.setOnClickListener {
activity?.popCurrentPage()
observe(viewModel.recommendations) { recommendations ->
setRecommendations(recommendations, null)
}
observe(viewModel.episodeSynopsis) { description ->
view.context?.let { ctx ->
val builder: AlertDialog.Builder =
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
builder.setMessage(description.html())
.setTitle(R.string.synopsis)
.setOnDismissListener {
viewModel.releaseEpisodeSynopsis()
}
.show()
}
}
observeNullable(viewModel.episodes) { episodes ->
binding?.apply {
resultEpisodes.isVisible = episodes is Resource.Success
resultEpisodeLoading.isVisible = episodes is Resource.Loading
if (episodes is Resource.Success) {
/*
* Okay so what is this fuckery?
* Basically Android TV will crash if you request a new focus while
* the adapter gets updated.
*
* This means that if you load thumbnails and request a next focus at the same time
* the app will crash without any way to catch it!
*
* How to bypass this?
* This code basically steals the focus for 500ms and puts it in an inescapable view
* then lets out the focus by requesting focus to result_episodes
*/
resultRecommendationsList.spanCount = 8
resultRecommendationsList.adapter =
SearchAdapter(
ArrayList(),
resultRecommendationsList,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
val hasEpisodes =
!(resultEpisodes.adapter as? EpisodeAdapter?)?.cardList.isNullOrEmpty()
if (hasEpisodes) {
// Make it impossible to focus anywhere else!
temporaryNoFocus.isFocusable = true
temporaryNoFocus.requestFocus()
}
(resultEpisodes.adapter as? EpisodeAdapter)?.updateList(episodes.value)
if (hasEpisodes) main {
delay(500)
temporaryNoFocus.isFocusable = false
// This might make some people sad as it changes the focus when leaving an episode :(
temporaryNoFocus.requestFocus()
}
if (hasNoFocus())
binding?.resultEpisodes?.requestFocus()
}
}
}
observeNullable(viewModel.page) { data ->
if (data == null) return@observeNullable
binding?.apply {
when (data) {
is Resource.Success -> {
val d = data.value
resultVpn.setText(d.vpnText)
resultInfo.setText(d.metaText)
resultNoEpisodes.setText(d.noEpisodesFoundText)
resultTitle.setText(d.titleText)
resultMetaSite.setText(d.apiName)
resultMetaType.setText(d.typeText)
resultMetaYear.setText(d.yearText)
resultMetaDuration.setText(d.durationText)
resultMetaRating.setText(d.ratingText)
resultCastText.setText(d.actorsText)
resultNextAiring.setText(d.nextAiringEpisode)
resultNextAiringTime.setText(d.nextAiringDate)
resultPoster.setImage(d.posterImage)
resultDescription.setTextHtml(d.plotText)
resultComingSoon.isVisible = d.comingSoon
resultDataHolder.isGone = d.comingSoon
UIHelper.populateChips(resultTag, d.tags)
resultCastItems.isGone = d.actors.isNullOrEmpty()
(resultCastItems.adapter as? ActorAdaptor)?.updateList(
d.actors ?: emptyList()
)
}
is Resource.Loading -> {
}
is Resource.Failure -> {
resultErrorText.text =
(this@ResultFragmentTv.context?.let { getStoredData(it) }?.url?.plus("\n")
?: "") + data.errorString
}
}
resultFinishLoading.isVisible = data is Resource.Success
resultLoading.isVisible = data is Resource.Loading
resultLoadingError.isVisible = data is Resource.Failure
resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure
}
}
}
}

View file

@ -10,21 +10,13 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.discord.panels.PanelsChildGestureRegionObserver
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.player.FullScreenPlayer
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.IOnBackPressed
import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.android.synthetic.main.fragment_result.result_smallscreen_holder
import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.fragment_trailer.*
open class ResultTrailerPlayer : FullScreenPlayer(),
PanelsChildGestureRegionObserver.GestureRegionsListener, IOnBackPressed {
open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
override var lockRotation = false
override var isFullScreenPlayer = false
@ -60,13 +52,13 @@ open class ResultTrailerPlayer : FullScreenPlayer(),
screenHeight
}
result_trailer_loading?.isVisible = false
result_smallscreen_holder?.isVisible = !isFullScreenPlayer
result_fullscreen_holder?.isVisible = isFullScreenPlayer
//result_trailer_loading?.isVisible = false
resultBinding?.resultSmallscreenHolder?.isVisible = !isFullScreenPlayer
binding?.resultFullscreenHolder?.isVisible = isFullScreenPlayer
val to = sw * h / w
player_background?.apply {
resultBinding?.fragmentTrailer?.playerBackground?.apply {
isVisible = true
layoutParams =
FrameLayout.LayoutParams(
@ -79,12 +71,13 @@ open class ResultTrailerPlayer : FullScreenPlayer(),
layoutParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
result_top_holder?.measuredHeight ?: FrameLayout.LayoutParams.MATCH_PARENT
resultBinding?.resultTopHolder?.measuredHeight
?: FrameLayout.LayoutParams.MATCH_PARENT
)
}
if (playerBinding?.playerIntroPlay?.isGone == true) {
result_top_holder?.apply {
resultBinding?.resultTopHolder?.apply {
val anim = ValueAnimator.ofInt(
measuredHeight,
@ -135,20 +128,26 @@ open class ResultTrailerPlayer : FullScreenPlayer(),
playerBinding?.playerFullscreen?.setImageResource(if (fullscreen) R.drawable.baseline_fullscreen_exit_24 else R.drawable.baseline_fullscreen_24)
if (fullscreen) {
enterFullscreen()
result_top_bar?.isVisible = false
result_fullscreen_holder?.isVisible = true
result_main_holder?.isVisible = false
player_background?.let { view ->
(view.parent as ViewGroup?)?.removeView(view)
result_fullscreen_holder?.addView(view)
binding?.apply {
resultTopBar.isVisible = false
resultFullscreenHolder.isVisible = true
resultMainHolder.isVisible = false
}
} else {
result_top_bar?.isVisible = true
result_fullscreen_holder?.isVisible = false
result_main_holder?.isVisible = true
player_background?.let { view ->
resultBinding?.fragmentTrailer?.playerBackground?.let { view ->
(view.parent as ViewGroup?)?.removeView(view)
result_smallscreen_holder?.addView(view)
binding?.resultFullscreenHolder?.addView(view)
}
} else {
binding?.apply {
resultTopBar.isVisible = true
resultFullscreenHolder.isVisible = false
resultMainHolder.isVisible = true
resultBinding?.fragmentTrailer?.playerBackground?.let { view ->
(view.parent as ViewGroup?)?.removeView(view)
resultBinding?.resultSmallscreenHolder?.addView(view)
}
}
exitFullscreen()
}

View file

@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit
object SyncUtil {
private val regexs = listOf(
Regex("""(9anime)\.(?:to|center|id)/watch/(?:.*?)\.([^/?]*)"""),
Regex("""(9anime)\.(?:to|center|id)/watch/.*?\.([^/?]*)"""),
Regex("""(gogoanime|gogoanimes)\..*?/category/([^/?]*)"""),
Regex("""(twist\.moe)/a/([^/?]*)"""),
)
@ -44,6 +44,13 @@ object SyncUtil {
matchList[site]?.let { realSite ->
getIdsFromSlug(slug, realSite)?.let {
return it
} ?: kotlin.run {
if (slug.endsWith("-dub")) {
println("testing non -dub slug $slug")
getIdsFromSlug(slug.removeSuffix("-dub"), realSite)?.let {
return it
}
}
}
}
}

View file

@ -46,12 +46,16 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions.bitmapTransform
import com.bumptech.glide.request.target.Target
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.google.android.material.chip.ChipGroup
import com.lagradost.cloudstream3.CommonActivity.activity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.UiImage
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlin.math.roundToInt
@ -73,6 +77,30 @@ object UIHelper {
|| Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
}
fun populateChips(view: ChipGroup?, tags : List<String>) {
if(view == null) return
view.removeAllViews()
val context = view.context ?: return
tags.forEach { tag ->
val chip = Chip(context)
val chipDrawable = ChipDrawable.createFromAttributes(
context,
null,
0,
R.style.ChipFilled
)
chip.setChipDrawable(chipDrawable)
chip.text = tag
chip.isChecked = false
chip.isCheckable = false
chip.isFocusable = false
chip.isClickable = false
chip.setTextColor(context.colorFromAttribute(R.attr.textColor))
view.addView(chip)
}
}
fun Activity.requestRW() {
ActivityCompat.requestPermissions(
this,

View file

@ -465,6 +465,7 @@
</com.google.android.material.button.MaterialButton>
<com.lagradost.cloudstream3.ui.download.button.DownloadButton
android:visibility="gone"
android:id="@+id/download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View file

@ -216,32 +216,6 @@
tools:visibility="visible" />
<!-- This nested layout is necessary because of buffering and clicking-->
<FrameLayout
android:id="@+id/player_pause_play_holder_holder"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/player_pause_play"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button"
android:nextFocusLeft="@id/exo_rew"
android:nextFocusRight="@id/exo_ffwd"
android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock"
android:src="@drawable/netflix_pause"
app:tint="@color/white"
tools:ignore="ContentDescription" />
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_center_menu"
@ -298,7 +272,32 @@
app:tint="@color/white"
tools:ignore="ContentDescription" />
</FrameLayout>
<FrameLayout
android:id="@+id/player_pause_play_holder_holder"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/player_pause_play"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button"
android:nextFocusLeft="@id/exo_rew"
android:nextFocusRight="@id/exo_ffwd"
android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock"
android:src="@drawable/netflix_pause"
app:tint="@color/white"
tools:ignore="ContentDescription" />
</FrameLayout>
<FrameLayout
android:id="@+id/player_ffwd_holder"
android:layout_width="0dp"

View file

@ -76,10 +76,10 @@
</FrameLayout>
<FrameLayout
android:visibility="gone"
android:id="@+id/player_top_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
@ -113,7 +113,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="2dp">
android:layout_marginTop="2dp"
android:visibility="gone">
<com.google.android.material.button.MaterialButton
android:id="@+id/player_episode_filler"
@ -136,9 +137,13 @@
<FrameLayout
android:id="@+id/player_go_back_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="5dp"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -206,33 +211,7 @@
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<!-- This nested layout is necessary because of buffering and clicking-->
<FrameLayout
android:id="@+id/player_pause_play_holder_holder"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/player_pause_play"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button"
android:nextFocusLeft="@id/exo_rew"
android:nextFocusRight="@id/exo_ffwd"
android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock"
android:src="@drawable/netflix_pause"
app:tint="@color/white"
tools:ignore="ContentDescription" />
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_center_menu"
@ -288,7 +267,33 @@
app:tint="@color/white"
tools:ignore="ContentDescription" />
</FrameLayout>
<!-- This nested layout is necessary because of buffering and clicking-->
<FrameLayout
android:id="@+id/player_pause_play_holder_holder"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/player_pause_play"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button"
android:nextFocusLeft="@id/exo_rew"
android:nextFocusRight="@id/exo_ffwd"
android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock"
android:src="@drawable/netflix_pause"
app:tint="@color/white"
tools:ignore="ContentDescription" />
</FrameLayout>
<FrameLayout
android:id="@+id/player_ffwd_holder"
android:layout_width="0dp"

View file

@ -436,7 +436,7 @@
<fragment
android:id="@+id/navigation_results_phone"
android:name="com.lagradost.cloudstream3.ui.result.ResultFragmentPhone"
android:name="com.lagradost.cloudstream3.ui.result.ResultTrailerPlayer"
android:layout_height="match_parent"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"