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") //implementation("com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT")
// newpipe yt taken from https://github.com/TeamNewPipe/NewPipe/blob/dev/app/build.gradle#L204 // 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") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
// Library/extensions searching with Levenshtein distance // Library/extensions searching with Levenshtein distance

View file

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

View file

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

View file

@ -74,15 +74,15 @@ abstract class AbstractPlayerFragment(
var isBuffering = true var isBuffering = true
protected open var hasPipModeSupport = true protected open var hasPipModeSupport = true
lateinit var playerPausePlayHolderHolder : FrameLayout var playerPausePlayHolderHolder : FrameLayout? = null
lateinit var playerPausePlay : ImageView var playerPausePlay : ImageView? = null
lateinit var playerBuffering : ProgressBar var playerBuffering : ProgressBar? = null
var playerView : PlayerView? = null var playerView : PlayerView? = null
var piphide : FrameLayout? = null var piphide : FrameLayout? = null
var subtitleHolder : FrameLayout? = null var subtitleHolder : FrameLayout? = null
@LayoutRes @LayoutRes
protected var layout: Int = R.layout.fragment_player protected open var layout: Int = R.layout.fragment_player
open fun nextEpisode() { open fun nextEpisode() {
throw NotImplementedError() throw NotImplementedError()
@ -141,15 +141,15 @@ abstract class AbstractPlayerFragment(
isBuffering = CSPlayerLoading.IsBuffering == isPlaying isBuffering = CSPlayerLoading.IsBuffering == isPlaying
if (isBuffering) { if (isBuffering) {
playerPausePlayHolderHolder.isVisible = false playerPausePlayHolderHolder?.isVisible = false
playerBuffering.isVisible = true playerBuffering?.isVisible = true
} else { } else {
playerPausePlayHolderHolder.isVisible = true playerPausePlayHolderHolder?.isVisible = true
playerBuffering.isVisible = false playerBuffering?.isVisible = false
if (wasPlaying != isPlaying) { if (wasPlaying != isPlaying) {
playerPausePlay.setImageResource(if (isPlayingRightNow) R.drawable.play_to_pause else R.drawable.pause_to_play) playerPausePlay?.setImageResource(if (isPlayingRightNow) R.drawable.play_to_pause else R.drawable.pause_to_play)
val drawable = playerPausePlay.drawable val drawable = playerPausePlay?.drawable
var startedAnimation = false var startedAnimation = false
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
@ -171,10 +171,10 @@ abstract class AbstractPlayerFragment(
// somehow the phone is wacked // somehow the phone is wacked
if (!startedAnimation) { 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 { } 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.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible 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.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick 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.result.EpisodeAdapter.Companion.getPlayerAction
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings 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.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import kotlinx.android.synthetic.main.fragment_result.download_button 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_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_tag_holder
import kotlinx.android.synthetic.main.fragment_result.result_title 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.result_vpn
import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.android.synthetic.main.fragment_result_tv.result_resume_series_button_play
import kotlinx.android.synthetic.main.fragment_result_tv.* import kotlinx.android.synthetic.main.fragment_result_tv.temporary_no_focus
import kotlinx.android.synthetic.main.result_sync.*
import kotlinx.android.synthetic.main.trailer_custom_layout.*
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -194,7 +188,7 @@ fun ResultEpisode.getWatchProgress(): Float {
return (getDisplayPosition() / 1000).toFloat() / (duration / 1000).toFloat() return (getDisplayPosition() / 1000).toFloat() / (duration / 1000).toFloat()
} }
open class ResultFragment : ResultTrailerPlayer() { open class ResultFragment : FullScreenPlayer() {
companion object { companion object {
const val URL_BUNDLE = "url" const val URL_BUNDLE = "url"
const val API_NAME_BUNDLE = "apiName" const val API_NAME_BUNDLE = "apiName"
@ -251,7 +245,9 @@ open class ResultFragment : ResultTrailerPlayer() {
protected lateinit var viewModel: ResultViewModel2 //by activityViewModels() protected lateinit var viewModel: ResultViewModel2 //by activityViewModels()
protected lateinit var syncModel: SyncViewModel 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( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -263,7 +259,8 @@ open class ResultFragment : ResultTrailerPlayer() {
syncModel = syncModel =
ViewModelProvider(this)[SyncViewModel::class.java] 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() { override fun onDestroyView() {
@ -285,167 +282,12 @@ open class ResultFragment : ResultTrailerPlayer() {
super.onDestroy() 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() { private fun updateUI() {
syncModel.updateUserData() syncModel.updateUserData()
viewModel.reloadEpisodes() 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( data class StoredData(
val url: String?, val url: String?,
val apiName: String, val apiName: String,
@ -455,7 +297,7 @@ open class ResultFragment : ResultTrailerPlayer() {
val playerAction: Int val playerAction: Int
) )
private fun getStoredData(context: Context): StoredData? { fun getStoredData(context: Context): StoredData? {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
val url = arguments?.getString(URL_BUNDLE) val url = arguments?.getString(URL_BUNDLE)
val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return null val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return null
@ -537,7 +379,6 @@ open class ResultFragment : ResultTrailerPlayer() {
context?.updateHasTrailers() context?.updateHasTrailers()
activity?.loadCache() activity?.loadCache()
fixPaddingStatusbar(result_top_bar)
//activity?.fixPaddingStatusbar(result_barstatus) //activity?.fixPaddingStatusbar(result_barstatus)
/* val backParameter = result_back.layoutParams as FrameLayout.LayoutParams /* val backParameter = result_back.layoutParams as FrameLayout.LayoutParams
@ -554,35 +395,6 @@ open class ResultFragment : ResultTrailerPlayer() {
val storedData = (activity ?: context)?.let { val storedData = (activity ?: context)?.let {
getStoredData(it) 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 // This is to band-aid FireTV navigation
val isTv = isTvSettings() val isTv = isTvSettings()
@ -638,215 +450,6 @@ open class ResultFragment : ResultTrailerPlayer() {
result_resume_series_button_play?.setOnClickListener(click) 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 -> context?.let { ctx ->
//result_bookmark_button?.isVisible = ctx.isTvSettings() //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 // bloats the navigation on tv
if (!isTrueTvSettings()) { if (!isTrueTvSettings()) {
result_meta_site?.setOnClickListener { result_meta_site?.setOnClickListener {

View file

@ -1,8 +1,11 @@
package com.lagradost.cloudstream3.ui.result package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Rect import android.graphics.Rect
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
@ -15,6 +18,7 @@ import android.view.animation.DecelerateInterpolator
import android.widget.AbsListView import android.widget.AbsListView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView 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.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
import com.lagradost.cloudstream3.ui.WatchType 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.CSPlayerEvent
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper 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.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser 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.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant import com.lagradost.cloudstream3.utils.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.colorFromAttribute
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage 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.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() { open class ResultFragmentPhone : ResultFragment(),
private var binding: FragmentResultSwipeBinding? = null PanelsChildGestureRegionObserver.GestureRegionsListener {
private var resultBinding: FragmentResultBinding? = null protected var binding: FragmentResultSwipeBinding? = null
private var recommendationBinding: ResultRecommendationsBinding? = null protected var resultBinding: FragmentResultBinding? = null
private var syncBinding: ResultSyncBinding? = null protected var recommendationBinding: ResultRecommendationsBinding? = null
protected var syncBinding: ResultSyncBinding? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -205,43 +227,158 @@ class ResultFragmentPhone : ResultFragment() {
var selectSeason: String? = null 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return
super.onViewCreated(view, savedInstanceState) 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 { val api = APIHolder.getApiFromNameNull(apiName)
currentTrailers.getOrNull(currentTrailerIndex)?.let {
context?.openBrowser(it.url) 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 { playerBinding?.apply {
spanCount = 3 playerOpenSource.setOnClickListener {
adapter = currentTrailers.getOrNull(currentTrailerIndex)?.let {
SearchAdapter( context?.openBrowser(it.url)
ArrayList(),
this,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
} }
}
}
recommendationBinding?.apply {
resultRecommendationsList.apply {
spanCount = 3
adapter =
SearchAdapter(
ArrayList(),
this,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
}
} }
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this) PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
resultBinding?.resultCastItems?.let {
PanelsChildGestureRegionObserver.Provider.get().register(it)
}
binding?.resultBack?.setOnClickListener {
activity?.popCurrentPage()
}
/* /*
result_bookmark_button?.setOnClickListener { result_bookmark_button?.setOnClickListener {
it.popupMenuNoIcons( it.popupMenuNoIcons(
@ -253,62 +390,165 @@ class ResultFragmentPhone : ResultFragment() {
} }
}*/ }*/
binding?.resultMiniSync?.adapter = ImageAdapter( observeNullable(viewModel.subscribeStatus) { isSubscribed ->
nextFocusDown = R.id.result_sync_set_score, binding?.resultSubscribe?.isVisible = isSubscribed != null
clickCallback = { action -> if (isSubscribed == null) return@observeNullable
if (action == IMAGE_CLICK || action == IMAGE_LONG_CLICK) {
if (binding?.resultOverlappingPanels?.getSelectedPanel()?.ordinal == 1) { val drawable = if (isSubscribed) {
binding?.resultOverlappingPanels?.openStartPanel() 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 { } 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 -> binding?.resultBookmarkFab?.isVisible = data is Resource.Success
val dy = scrollY - oldScrollY resultFinishLoading.isVisible = data is Resource.Success
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?.mediaRouteButton?.apply { resultLoading.isVisible = data is Resource.Loading
val chromecastSupport = api?.hasChromecastSupport == true
alpha = if (chromecastSupport) 1f else 0.3f resultLoadingError.isVisible = data is Resource.Failure
if (!chromecastSupport) { resultErrorText.isVisible = data is Resource.Failure
setOnClickListener { resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure
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)
}
}
} }
} }
@ -350,6 +590,7 @@ class ResultFragmentPhone : ResultFragment() {
(binding?.resultMiniSync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon }) (binding?.resultMiniSync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon })
} }
var currentSyncProgress = 0 var currentSyncProgress = 0
fun setSyncMaxEpisodes(totalEpisodes: Int?) { fun setSyncMaxEpisodes(totalEpisodes: Int?) {
syncBinding?.resultSyncEpisodes?.max = (totalEpisodes ?: 0) * 1000 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) 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 -> context?.let { ctx ->
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice) val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
/* /*
@ -653,7 +909,7 @@ class ResultFragmentPhone : ResultFragment() {
binding?.resultOverlappingPanels?.setChildGestureRegions(gestureRegions) 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 isInvalid = rec.isNullOrEmpty()
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName

View file

@ -1,11 +1,13 @@
package com.lagradost.cloudstream3.ui.result package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView 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.observe
import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.WatchType 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.ExtractorLinkGenerator
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper 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.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage 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() { class ResultFragmentTv : ResultFragment() {
override val resultLayout = R.layout.fragment_result_tv override var layout = R.layout.fragment_result_tv
private var binding: FragmentResultTvBinding? = null private var binding: FragmentResultTvBinding? = null
@ -95,20 +110,6 @@ class ResultFragmentTv : ResultFragment() {
return focus == binding?.resultRoot 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>?) { override fun setTrailers(trailers: List<ExtractorLink>?) {
context?.updateHasTrailers() context?.updateHasTrailers()
if (!LoadResponse.isTrailersEnabled) return 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() currentRecommendations = rec ?: emptyList()
val isInvalid = rec.isNullOrEmpty() val isInvalid = rec.isNullOrEmpty()
binding?.apply { binding?.apply {
@ -151,6 +152,8 @@ class ResultFragmentTv : ResultFragment() {
var loadingDialog: Dialog? = null var loadingDialog: Dialog? = null
var popupDialog: Dialog? = null var popupDialog: Dialog? = null
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding?.apply { binding?.apply {
@ -163,6 +166,34 @@ class ResultFragmentTv : ResultFragment() {
resultRangeSelection.setAdapter() resultRangeSelection.setAdapter()
resultDubSelection.setAdapter() resultDubSelection.setAdapter()
resultRecommendationsFilterSelection.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 -> 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 -> observeNullable(viewModel.selectPopup) { popup ->
if (popup == null) { if (popup == null) {
@ -257,21 +312,111 @@ class ResultFragmentTv : ResultFragment() {
observe(viewModel.seasonSelections) { observe(viewModel.seasonSelections) {
binding?.resultSeasonSelection.update(it) binding?.resultSeasonSelection.update(it)
} }
observe(viewModel.recommendations) { recommendations ->
binding?.apply { setRecommendations(recommendations, null)
resultBack.setOnClickListener { }
activity?.popCurrentPage() 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 val hasEpisodes =
resultRecommendationsList.adapter = !(resultEpisodes.adapter as? EpisodeAdapter?)?.cardList.isNullOrEmpty()
SearchAdapter(
ArrayList(), if (hasEpisodes) {
resultRecommendationsList, // Make it impossible to focus anywhere else!
) { callback -> temporaryNoFocus.isFocusable = true
SearchHelper.handleSearchClickCallback(callback) 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 android.widget.FrameLayout
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.discord.panels.PanelsChildGestureRegionObserver
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent 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.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.IOnBackPressed 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(), open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
PanelsChildGestureRegionObserver.GestureRegionsListener, IOnBackPressed {
override var lockRotation = false override var lockRotation = false
override var isFullScreenPlayer = false override var isFullScreenPlayer = false
@ -60,13 +52,13 @@ open class ResultTrailerPlayer : FullScreenPlayer(),
screenHeight screenHeight
} }
result_trailer_loading?.isVisible = false //result_trailer_loading?.isVisible = false
result_smallscreen_holder?.isVisible = !isFullScreenPlayer resultBinding?.resultSmallscreenHolder?.isVisible = !isFullScreenPlayer
result_fullscreen_holder?.isVisible = isFullScreenPlayer binding?.resultFullscreenHolder?.isVisible = isFullScreenPlayer
val to = sw * h / w val to = sw * h / w
player_background?.apply { resultBinding?.fragmentTrailer?.playerBackground?.apply {
isVisible = true isVisible = true
layoutParams = layoutParams =
FrameLayout.LayoutParams( FrameLayout.LayoutParams(
@ -79,12 +71,13 @@ open class ResultTrailerPlayer : FullScreenPlayer(),
layoutParams = layoutParams =
FrameLayout.LayoutParams( FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT,
result_top_holder?.measuredHeight ?: FrameLayout.LayoutParams.MATCH_PARENT resultBinding?.resultTopHolder?.measuredHeight
?: FrameLayout.LayoutParams.MATCH_PARENT
) )
} }
if (playerBinding?.playerIntroPlay?.isGone == true) { if (playerBinding?.playerIntroPlay?.isGone == true) {
result_top_holder?.apply { resultBinding?.resultTopHolder?.apply {
val anim = ValueAnimator.ofInt( val anim = ValueAnimator.ofInt(
measuredHeight, 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) playerBinding?.playerFullscreen?.setImageResource(if (fullscreen) R.drawable.baseline_fullscreen_exit_24 else R.drawable.baseline_fullscreen_24)
if (fullscreen) { if (fullscreen) {
enterFullscreen() enterFullscreen()
result_top_bar?.isVisible = false binding?.apply {
result_fullscreen_holder?.isVisible = true resultTopBar.isVisible = false
result_main_holder?.isVisible = false resultFullscreenHolder.isVisible = true
player_background?.let { view -> resultMainHolder.isVisible = false
(view.parent as ViewGroup?)?.removeView(view)
result_fullscreen_holder?.addView(view)
} }
} else {
result_top_bar?.isVisible = true resultBinding?.fragmentTrailer?.playerBackground?.let { view ->
result_fullscreen_holder?.isVisible = false
result_main_holder?.isVisible = true
player_background?.let { view ->
(view.parent as ViewGroup?)?.removeView(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() exitFullscreen()
} }

View file

@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit
object SyncUtil { object SyncUtil {
private val regexs = listOf( private val regexs = listOf(
Regex("""(9anime)\.(?:to|center|id)/watch/(?:.*?)\.([^/?]*)"""), Regex("""(9anime)\.(?:to|center|id)/watch/.*?\.([^/?]*)"""),
Regex("""(gogoanime|gogoanimes)\..*?/category/([^/?]*)"""), Regex("""(gogoanime|gogoanimes)\..*?/category/([^/?]*)"""),
Regex("""(twist\.moe)/a/([^/?]*)"""), Regex("""(twist\.moe)/a/([^/?]*)"""),
) )
@ -44,6 +44,13 @@ object SyncUtil {
matchList[site]?.let { realSite -> matchList[site]?.let { realSite ->
getIdsFromSlug(slug, realSite)?.let { getIdsFromSlug(slug, realSite)?.let {
return it 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.RequestListener
import com.bumptech.glide.request.RequestOptions.bitmapTransform import com.bumptech.glide.request.RequestOptions.bitmapTransform
import com.bumptech.glide.request.target.Target 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.CommonActivity.activity
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.UiImage import com.lagradost.cloudstream3.ui.result.UiImage
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -73,6 +77,30 @@ object UIHelper {
|| Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) || 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() { fun Activity.requestRW() {
ActivityCompat.requestPermissions( ActivityCompat.requestPermissions(
this, this,

View file

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

View file

@ -216,32 +216,6 @@
tools:visibility="visible" /> tools:visibility="visible" />
<!-- This nested layout is necessary because of buffering and clicking--> <!-- 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 <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_center_menu" android:id="@+id/player_center_menu"
@ -298,7 +272,32 @@
app:tint="@color/white" app:tint="@color/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</FrameLayout> </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 <FrameLayout
android:id="@+id/player_ffwd_holder" android:id="@+id/player_ffwd_holder"
android:layout_width="0dp" android:layout_width="0dp"

View file

@ -76,10 +76,10 @@
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
android:visibility="gone"
android:id="@+id/player_top_holder" android:id="@+id/player_top_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:visibility="gone">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -113,7 +113,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:layout_marginTop="2dp"> android:layout_marginTop="2dp"
android:visibility="gone">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/player_episode_filler" android:id="@+id/player_episode_filler"
@ -136,9 +137,13 @@
<FrameLayout <FrameLayout
android:id="@+id/player_go_back_holder" android:id="@+id/player_go_back_holder"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_margin="5dp" android:layout_margin="5dp"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -206,33 +211,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" /> 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 <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_center_menu" android:id="@+id/player_center_menu"
@ -288,7 +267,33 @@
app:tint="@color/white" app:tint="@color/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</FrameLayout> </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 <FrameLayout
android:id="@+id/player_ffwd_holder" android:id="@+id/player_ffwd_holder"
android:layout_width="0dp" android:layout_width="0dp"

View file

@ -436,7 +436,7 @@
<fragment <fragment
android:id="@+id/navigation_results_phone" 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" android:layout_height="match_parent"
app:enterAnim="@anim/enter_anim" app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim" app:exitAnim="@anim/exit_anim"