more result bindings + player

This commit is contained in:
LagradOst 2023-07-18 03:55:00 +02:00
parent 4f28aef8f2
commit d5c42f7d5a
14 changed files with 1321 additions and 916 deletions

View file

@ -11,6 +11,8 @@ import com.lagradost.cloudstream3.databinding.FragmentHomeBinding
import com.lagradost.cloudstream3.databinding.FragmentHomeTvBinding import com.lagradost.cloudstream3.databinding.FragmentHomeTvBinding
import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding
import com.lagradost.cloudstream3.databinding.FragmentPlayerTvBinding import com.lagradost.cloudstream3.databinding.FragmentPlayerTvBinding
import com.lagradost.cloudstream3.databinding.FragmentResultBinding
import com.lagradost.cloudstream3.databinding.FragmentResultTvBinding
import com.lagradost.cloudstream3.databinding.FragmentSearchBinding import com.lagradost.cloudstream3.databinding.FragmentSearchBinding
import com.lagradost.cloudstream3.databinding.FragmentSearchTvBinding import com.lagradost.cloudstream3.databinding.FragmentSearchTvBinding
import com.lagradost.cloudstream3.databinding.HomeResultGridBinding import com.lagradost.cloudstream3.databinding.HomeResultGridBinding
@ -22,6 +24,7 @@ import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding
import com.lagradost.cloudstream3.databinding.SearchResultGridBinding import com.lagradost.cloudstream3.databinding.SearchResultGridBinding
import com.lagradost.cloudstream3.databinding.SearchResultGridExpandedBinding import com.lagradost.cloudstream3.databinding.SearchResultGridExpandedBinding
import com.lagradost.cloudstream3.databinding.TrailerCustomLayoutBinding
import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.TestingUtils import com.lagradost.cloudstream3.utils.TestingUtils
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -85,8 +88,12 @@ class ExampleInstrumentedTest {
testAllLayouts<FragmentPlayerBinding>(activity, R.layout.fragment_player,R.layout.fragment_player_tv) testAllLayouts<FragmentPlayerBinding>(activity, R.layout.fragment_player,R.layout.fragment_player_tv)
testAllLayouts<FragmentPlayerTvBinding>(activity, R.layout.fragment_player,R.layout.fragment_player_tv) testAllLayouts<FragmentPlayerTvBinding>(activity, R.layout.fragment_player,R.layout.fragment_player_tv)
testAllLayouts<PlayerCustomLayoutBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv) // testAllLayouts<FragmentResultBinding>(activity, R.layout.fragment_result,R.layout.fragment_result_tv)
testAllLayouts<PlayerCustomLayoutTvBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv) // testAllLayouts<FragmentResultTvBinding>(activity, R.layout.fragment_result,R.layout.fragment_result_tv)
testAllLayouts<PlayerCustomLayoutBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout)
testAllLayouts<PlayerCustomLayoutTvBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout)
testAllLayouts<TrailerCustomLayoutBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout)
testAllLayouts<RepositoryItemBinding>(activity, R.layout.repository_item_tv, R.layout.repository_item) testAllLayouts<RepositoryItemBinding>(activity, R.layout.repository_item_tv, R.layout.repository_item)
testAllLayouts<RepositoryItemTvBinding>(activity, R.layout.repository_item_tv, R.layout.repository_item) testAllLayouts<RepositoryItemTvBinding>(activity, R.layout.repository_item_tv, R.layout.repository_item)

View file

@ -92,7 +92,6 @@ class GeneratorPlayer : FullScreenPlayer() {
private var preferredAutoSelectSubtitles: String? = null // null means do nothing, "" means none private var preferredAutoSelectSubtitles: String? = null // null means do nothing, "" means none
private var binding: FragmentPlayerBinding? = null private var binding: FragmentPlayerBinding? = null
private var playerBinding: PlayerCustomLayoutBinding? = null
private fun startLoading() { private fun startLoading() {
player.release() player.release()
@ -1236,15 +1235,11 @@ class GeneratorPlayer : FullScreenPlayer() {
unwrapBundle(arguments) unwrapBundle(arguments)
val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null
binding = FragmentPlayerBinding.bind(root).also { b -> binding = FragmentPlayerBinding.bind(root)
playerBinding = PlayerCustomLayoutBinding.bind(b.playerView.findViewById(R.id.player_holder))
}
return root return root
} }
override fun onDestroyView() { override fun onDestroyView() {
playerBinding = null
binding = null binding = null
super.onDestroyView() super.onDestroyView()
} }

View file

@ -4,26 +4,19 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.* import android.content.Intent.*
import android.content.res.ColorStateList
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.Editable
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 android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.ImageView import android.widget.ImageView
import android.widget.Toast 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
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.discord.panels.OverlappingPanelsLayout
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable import com.google.android.material.chip.ChipDrawable
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
@ -38,7 +31,6 @@ 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.services.SubscriptionWorkManager
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.WatchType
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.quicksearch.QuickSearchFragment
@ -53,7 +45,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStoreHelper.getVideoWatchState 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.SingleSelectionHelper.showBottomDialog
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.fixPaddingStatusbar
@ -364,8 +355,9 @@ open class ResultFragment : ResultTrailerPlayer() {
return@setOnLongClickListener true return@setOnLongClickListener true
} }
val show = viewModel.currentRepo?.api?.hasDownloadSupport == true && !isTvSettings() val show =
if(show) { viewModel.currentRepo?.api?.hasDownloadSupport == true && !isTvSettings()
if (show) {
download_button?.setDefaultClickListener( download_button?.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached( VideoDownloadHelper.DownloadEpisodeCached(
ep.name, ep.name,
@ -379,7 +371,6 @@ open class ResultFragment : ResultTrailerPlayer() {
System.currentTimeMillis(), System.currentTimeMillis(),
) )
) { click -> ) { click ->
println("Click:$click")
when (click.action) { when (click.action) {
DOWNLOAD_ACTION_DOWNLOAD -> { DOWNLOAD_ACTION_DOWNLOAD -> {
viewModel.handleAction( viewModel.handleAction(
@ -393,7 +384,6 @@ open class ResultFragment : ResultTrailerPlayer() {
} }
} }
download_button?.isVisible = show download_button?.isVisible = show
} }
} }
@ -593,42 +583,6 @@ open class ResultFragment : ResultTrailerPlayer() {
} }
} }
observe(viewModel.watchStatus) { watchType ->
result_bookmark_button?.text = getString(watchType.stringRes)
result_bookmark_fab?.text = getString(watchType.stringRes)
if (watchType == WatchType.NONE) {
result_bookmark_fab?.context?.colorFromAttribute(R.attr.white)
} else {
result_bookmark_fab?.context?.colorFromAttribute(R.attr.colorPrimary)
}?.let {
val colorState = ColorStateList.valueOf(it)
result_bookmark_fab?.iconTint = colorState
result_bookmark_fab?.setTextColor(colorState)
}
result_bookmark_fab?.setOnClickListener { fab ->
activity?.showBottomDialog(
WatchType.values().map { fab.context.getString(it.stringRes) }.toList(),
watchType.ordinal,
fab.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
viewModel.updateWatchStatus(WatchType.values()[it])
}
}
result_bookmark_button?.setOnClickListener { fab ->
activity?.showBottomDialog(
WatchType.values().map { fab.context.getString(it.stringRes) }.toList(),
watchType.ordinal,
fab.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
viewModel.updateWatchStatus(WatchType.values()[it])
}
}
}
// This is to band-aid FireTV navigation // This is to band-aid FireTV navigation
val isTv = isTvSettings() val isTv = isTvSettings()
@ -636,152 +590,6 @@ open class ResultFragment : ResultTrailerPlayer() {
result_episode_select?.isFocusableInTouchMode = isTv result_episode_select?.isFocusableInTouchMode = isTv
result_dub_select?.isFocusableInTouchMode = isTv result_dub_select?.isFocusableInTouchMode = isTv
context?.let { ctx ->
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
/*
-1 -> None
0 -> Watching
1 -> Completed
2 -> OnHold
3 -> Dropped
4 -> PlanToWatch
5 -> ReWatching
*/
val items = listOf(
R.string.none,
R.string.type_watching,
R.string.type_completed,
R.string.type_on_hold,
R.string.type_dropped,
R.string.type_plan_to_watch,
R.string.type_re_watching
).map { ctx.getString(it) }
arrayAdapter.addAll(items)
result_sync_check?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
result_sync_check?.adapter = arrayAdapter
UIHelper.setListViewHeightBasedOnItems(result_sync_check)
result_sync_check?.setOnItemClickListener { _, _, which, _ ->
syncModel.setStatus(which - 1)
}
result_sync_rating?.addOnChangeListener { _, value, _ ->
syncModel.setScore(value.toInt())
}
result_sync_add_episode?.setOnClickListener {
syncModel.setEpisodesDelta(1)
}
result_sync_sub_episode?.setOnClickListener {
syncModel.setEpisodesDelta(-1)
}
result_sync_current_episodes?.doOnTextChanged { text, _, before, count ->
if (count == before) return@doOnTextChanged
text?.toString()?.toIntOrNull()?.let { ep ->
syncModel.setEpisodes(ep)
}
}
}
observe(syncModel.synced) { list ->
result_sync_names?.text =
list.filter { it.isSynced && it.hasAccount }.joinToString { it.name }
val newList = list.filter { it.isSynced && it.hasAccount }
result_mini_sync?.isVisible = newList.isNotEmpty()
(result_mini_sync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon })
}
var currentSyncProgress = 0
fun setSyncMaxEpisodes(totalEpisodes: Int?) {
result_sync_episodes?.max = (totalEpisodes ?: 0) * 1000
normalSafeApiCall {
val ctx = result_sync_max_episodes?.context
result_sync_max_episodes?.text =
totalEpisodes?.let { episodes ->
ctx?.getString(R.string.sync_total_episodes_some)?.format(episodes)
} ?: run {
ctx?.getString(R.string.sync_total_episodes_none)
}
}
}
observe(syncModel.metadata) { meta ->
when (meta) {
is Resource.Success -> {
val d = meta.value
result_sync_episodes?.progress = currentSyncProgress * 1000
setSyncMaxEpisodes(d.totalEpisodes)
viewModel.setMeta(d, syncModel.getSyncs())
}
is Resource.Loading -> {
result_sync_max_episodes?.text =
result_sync_max_episodes?.context?.getString(R.string.sync_total_episodes_none)
}
else -> {}
}
}
observe(syncModel.userData) { status ->
var closed = false
when (status) {
is Resource.Failure -> {
result_sync_loading_shimmer?.stopShimmer()
result_sync_loading_shimmer?.isVisible = false
result_sync_holder?.isVisible = false
closed = true
}
is Resource.Loading -> {
result_sync_loading_shimmer?.startShimmer()
result_sync_loading_shimmer?.isVisible = true
result_sync_holder?.isVisible = false
}
is Resource.Success -> {
result_sync_loading_shimmer?.stopShimmer()
result_sync_loading_shimmer?.isVisible = false
result_sync_holder?.isVisible = true
val d = status.value
result_sync_rating?.value = d.score?.toFloat() ?: 0.0f
result_sync_check?.setItemChecked(d.status + 1, true)
val watchedEpisodes = d.watchedEpisodes ?: 0
currentSyncProgress = watchedEpisodes
d.maxEpisodes?.let {
// don't directly call it because we don't want to override metadata observe
setSyncMaxEpisodes(it)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
result_sync_episodes?.setProgress(watchedEpisodes * 1000, true)
} else {
result_sync_episodes?.progress = watchedEpisodes * 1000
}
result_sync_current_episodes?.text =
Editable.Factory.getInstance()?.newEditable(watchedEpisodes.toString())
normalSafeApiCall { // format might fail
context?.getString(R.string.sync_score_format)?.format(d.score ?: 0)?.let {
result_sync_score_text?.text = it
}
}
}
null -> {
closed = false
}
}
result_overlapping_panels?.setStartPanelLockState(if (closed) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
}
observeNullable(viewModel.resumeWatching) { resume -> observeNullable(viewModel.resumeWatching) { resume ->
if (resume == null) { if (resume == null) {
@ -841,10 +649,6 @@ open class ResultFragment : ResultTrailerPlayer() {
if (hasFocus) result_bookmark_button?.requestFocus() if (hasFocus) result_bookmark_button?.requestFocus()
} }
result_sync_set_score?.setOnClickListener {
syncModel.publishUserData()
}
observe(viewModel.trailers) { trailers -> observe(viewModel.trailers) { trailers ->
setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet! setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet!
} }

View file

@ -1,28 +1,47 @@
package com.lagradost.cloudstream3.ui.result package com.lagradost.cloudstream3.ui.result
import android.app.Dialog import android.app.Dialog
import android.content.res.ColorStateList
import android.graphics.Rect import android.graphics.Rect
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AlphaAnimation import android.view.animation.AlphaAnimation
import android.view.animation.Animation import android.view.animation.Animation
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.Toast import android.widget.Toast
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
import androidx.core.widget.doOnTextChanged
import com.discord.panels.OverlappingPanelsLayout import com.discord.panels.OverlappingPanelsLayout
import com.discord.panels.PanelsChildGestureRegionObserver import com.discord.panels.PanelsChildGestureRegionObserver
import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState import com.google.android.gms.cast.framework.CastState
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.databinding.FragmentResultBinding
import com.lagradost.cloudstream3.databinding.FragmentResultSwipeBinding
import com.lagradost.cloudstream3.databinding.ResultRecommendationsBinding
import com.lagradost.cloudstream3.databinding.ResultSyncBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
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.ui.WatchType
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
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
@ -32,25 +51,35 @@ 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.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.android.synthetic.main.fragment_result.result_cast_items
import kotlinx.android.synthetic.main.fragment_result.result_episodes_text
import kotlinx.android.synthetic.main.fragment_result.result_resume_parent
import kotlinx.android.synthetic.main.fragment_result.result_scroll
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_swipe.result_back
import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.fragment_trailer.*
import kotlinx.android.synthetic.main.result_recommendations.*
import kotlinx.android.synthetic.main.result_recommendations.result_recommendations
import kotlinx.android.synthetic.main.trailer_custom_layout.*
class ResultFragmentPhone : ResultFragment() { class ResultFragmentPhone : ResultFragment() {
private var binding: FragmentResultSwipeBinding? = null
private var resultBinding: FragmentResultBinding? = null
private var recommendationBinding: ResultRecommendationsBinding? = null
private var syncBinding: ResultSyncBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null
FragmentResultSwipeBinding.bind(root).let { bind ->
resultBinding =
bind.fragmentResult//FragmentResultBinding.bind(binding.root.findViewById(R.id.fragment_result))
recommendationBinding = bind.resultRecommendations
syncBinding = bind.resultSync
binding = bind
}
return root
}
var currentTrailers: List<ExtractorLink> = emptyList() var currentTrailers: List<ExtractorLink> = emptyList()
var currentTrailerIndex = 0 var currentTrailerIndex = 0
@ -96,19 +125,30 @@ class ResultFragmentPhone : ResultFragment() {
//result_trailer_thumbnail?.setImageBitmap(result_poster_background?.drawable?.toBitmap()) //result_trailer_thumbnail?.setImageBitmap(result_poster_background?.drawable?.toBitmap())
result_trailer_loading?.isVisible = isSuccess // result_trailer_loading?.isVisible = isSuccess
val turnVis = !isSuccess && !isFullScreenPlayer val turnVis = !isSuccess && !isFullScreenPlayer
result_smallscreen_holder?.isVisible = turnVis resultBinding?.apply {
result_poster_background_holder?.apply { resultSmallscreenHolder.isVisible = turnVis
val fadeIn: Animation = AlphaAnimation(alpha, if (turnVis) 1.0f else 0.0f).apply { resultPosterBackgroundHolder.apply {
interpolator = DecelerateInterpolator() val fadeIn: Animation = AlphaAnimation(alpha, if (turnVis) 1.0f else 0.0f).apply {
duration = 200 interpolator = DecelerateInterpolator()
fillAfter = true duration = 200
fillAfter = true
}
clearAnimation()
startAnimation(fadeIn)
} }
clearAnimation()
startAnimation(fadeIn) // We don't want the trailer to be focusable if it's not visible
resultSmallscreenHolder.descendantFocusability = if (isSuccess) {
ViewGroup.FOCUS_AFTER_DESCENDANTS
} else {
ViewGroup.FOCUS_BLOCK_DESCENDANTS
}
binding?.resultFullscreenHolder?.isVisible = !isSuccess && isFullScreenPlayer
} }
//player_view?.apply { //player_view?.apply {
//alpha = 0.0f //alpha = 0.0f
//ObjectAnimator.ofFloat(player_view, "alpha", 1f).apply { //ObjectAnimator.ofFloat(player_view, "alpha", 1f).apply {
@ -124,13 +164,7 @@ class ResultFragmentPhone : ResultFragment() {
//startAnimation(fadeIn) //startAnimation(fadeIn)
// } // }
// We don't want the trailer to be focusable if it's not visible
result_smallscreen_holder?.descendantFocusability = if (isSuccess) {
ViewGroup.FOCUS_AFTER_DESCENDANTS
} else {
ViewGroup.FOCUS_BLOCK_DESCENDANTS
}
result_fullscreen_holder?.isVisible = !isSuccess && isFullScreenPlayer
} }
override fun setTrailers(trailers: List<ExtractorLink>?) { override fun setTrailers(trailers: List<ExtractorLink>?) {
@ -144,12 +178,15 @@ class ResultFragmentPhone : ResultFragment() {
//somehow this still leaks and I dont know why???? //somehow this still leaks and I dont know why????
// todo look at https://github.com/discord/OverlappingPanels/blob/70b4a7cf43c6771873b1e091029d332896d41a1a/sample_app/src/main/java/com/discord/sampleapp/MainActivity.kt // todo look at https://github.com/discord/OverlappingPanels/blob/70b4a7cf43c6771873b1e091029d332896d41a1a/sample_app/src/main/java/com/discord/sampleapp/MainActivity.kt
PanelsChildGestureRegionObserver.Provider.get().let { obs -> PanelsChildGestureRegionObserver.Provider.get().let { obs ->
result_cast_items?.let { resultBinding?.resultCastItems?.let {
obs.unregister(it) obs.unregister(it)
} }
obs.removeGestureRegionsUpdateListener(this) obs.removeGestureRegionsUpdateListener(this)
} }
binding = null
resultBinding = null
syncBinding = null
recommendationBinding = null
super.onDestroyView() super.onDestroyView()
} }
@ -173,30 +210,35 @@ class ResultFragmentPhone : ResultFragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
player_open_source?.setOnClickListener {
playerBinding?.playerOpenSource?.setOnClickListener {
currentTrailers.getOrNull(currentTrailerIndex)?.let { currentTrailers.getOrNull(currentTrailerIndex)?.let {
context?.openBrowser(it.url) context?.openBrowser(it.url)
} }
} }
result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE) binding?.resultOverlappingPanels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
result_overlapping_panels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE) binding?.resultOverlappingPanels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
recommendationBinding?.resultRecommendationsList?.apply {
spanCount = 3
adapter =
SearchAdapter(
ArrayList(),
this,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
}
result_recommendations?.spanCount = 3
result_recommendations?.adapter =
SearchAdapter(
ArrayList(),
result_recommendations,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this) PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
result_cast_items?.let { resultBinding?.resultCastItems?.let {
PanelsChildGestureRegionObserver.Provider.get().register(it) PanelsChildGestureRegionObserver.Provider.get().register(it)
} }
result_back?.setOnClickListener { binding?.resultBack?.setOnClickListener {
activity?.popCurrentPage() activity?.popCurrentPage()
} }
@ -211,28 +253,30 @@ class ResultFragmentPhone : ResultFragment() {
} }
}*/ }*/
result_mini_sync?.adapter = ImageAdapter( binding?.resultMiniSync?.adapter = ImageAdapter(
nextFocusDown = R.id.result_sync_set_score, nextFocusDown = R.id.result_sync_set_score,
clickCallback = { action -> clickCallback = { action ->
if (action == IMAGE_CLICK || action == IMAGE_LONG_CLICK) { if (action == IMAGE_CLICK || action == IMAGE_LONG_CLICK) {
if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) { if (binding?.resultOverlappingPanels?.getSelectedPanel()?.ordinal == 1) {
result_overlapping_panels?.openStartPanel() binding?.resultOverlappingPanels?.openStartPanel()
} else { } else {
result_overlapping_panels?.closePanels() binding?.resultOverlappingPanels?.closePanels()
} }
} }
}) })
result_scroll?.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY -> resultBinding?.resultScroll?.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY val dy = scrollY - oldScrollY
if (dy > 0) { //check for scroll down if (dy > 0) { //check for scroll down
result_bookmark_fab?.shrink() binding?.resultBookmarkFab?.shrink()
} else if (dy < -5) { } else if (dy < -5) {
result_bookmark_fab?.extend() binding?.resultBookmarkFab?.extend()
} }
if (!isFullScreenPlayer && player.getIsPlaying()) { if (!isFullScreenPlayer && player.getIsPlaying()) {
if (scrollY > (player_background?.height ?: scrollY)) { if (scrollY > (resultBinding?.fragmentTrailer?.playerBackground?.height
?: scrollY)
) {
player.handleEvent(CSPlayerEvent.Pause) player.handleEvent(CSPlayerEvent.Pause)
} }
} }
@ -240,11 +284,11 @@ class ResultFragmentPhone : ResultFragment() {
}) })
val api = APIHolder.getApiFromNameNull(apiName) val api = APIHolder.getApiFromNameNull(apiName)
if (media_route_button != null) { binding?.mediaRouteButton?.apply {
val chromecastSupport = api?.hasChromecastSupport == true val chromecastSupport = api?.hasChromecastSupport == true
media_route_button?.alpha = if (chromecastSupport) 1f else 0.3f alpha = if (chromecastSupport) 1f else 0.3f
if (!chromecastSupport) { if (!chromecastSupport) {
media_route_button?.setOnClickListener { setOnClickListener {
CommonActivity.showToast( CommonActivity.showToast(
R.string.no_chromecast_support_toast, R.string.no_chromecast_support_toast,
Toast.LENGTH_LONG Toast.LENGTH_LONG
@ -254,10 +298,9 @@ class ResultFragmentPhone : ResultFragment() {
activity?.let { act -> activity?.let { act ->
if (act.isCastApiAvailable()) { if (act.isCastApiAvailable()) {
try { try {
CastButtonFactory.setUpMediaRouteButton(act, media_route_button) CastButtonFactory.setUpMediaRouteButton(act, this)
val castContext = CastContext.getSharedInstance(act.applicationContext) val castContext = CastContext.getSharedInstance(act.applicationContext)
media_route_button?.isGone = isGone = castContext.castState == CastState.NO_DEVICES_AVAILABLE
castContext.castState == CastState.NO_DEVICES_AVAILABLE
// this shit leaks for some reason // this shit leaks for some reason
//castContext.addCastStateListener { state -> //castContext.addCastStateListener { state ->
// media_route_button?.isGone = state == CastState.NO_DEVICES_AVAILABLE // media_route_button?.isGone = state == CastState.NO_DEVICES_AVAILABLE
@ -270,7 +313,7 @@ class ResultFragmentPhone : ResultFragment() {
} }
observeNullable(viewModel.episodesCountText) { count -> observeNullable(viewModel.episodesCountText) { count ->
result_episodes_text.setText(count) resultBinding?.resultEpisodesText.setText(count)
} }
observeNullable(viewModel.selectPopup) { popup -> observeNullable(viewModel.selectPopup) { popup ->
@ -295,10 +338,189 @@ class ResultFragmentPhone : ResultFragment() {
} }
) )
} }
} }
observe(syncModel.synced) { list ->
syncBinding?.resultSyncNames?.text =
list.filter { it.isSynced && it.hasAccount }.joinToString { it.name }
val newList = list.filter { it.isSynced && it.hasAccount }
binding?.resultMiniSync?.isVisible = newList.isNotEmpty()
(binding?.resultMiniSync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon })
}
var currentSyncProgress = 0
fun setSyncMaxEpisodes(totalEpisodes: Int?) {
syncBinding?.resultSyncEpisodes?.max = (totalEpisodes ?: 0) * 1000
normalSafeApiCall {
val ctx = syncBinding?.resultSyncEpisodes?.context
syncBinding?.resultSyncMaxEpisodes?.text =
totalEpisodes?.let { episodes ->
ctx?.getString(R.string.sync_total_episodes_some)?.format(episodes)
} ?: run {
ctx?.getString(R.string.sync_total_episodes_none)
}
}
}
observe(syncModel.metadata) { meta ->
when (meta) {
is Resource.Success -> {
val d = meta.value
syncBinding?.resultSyncEpisodes?.progress = currentSyncProgress * 1000
setSyncMaxEpisodes(d.totalEpisodes)
viewModel.setMeta(d, syncModel.getSyncs())
}
is Resource.Loading -> {
syncBinding?.resultSyncMaxEpisodes?.text =
syncBinding?.resultSyncMaxEpisodes?.context?.getString(R.string.sync_total_episodes_none)
}
else -> {}
}
}
observe(syncModel.userData) { status ->
var closed = false
syncBinding?.apply {
when (status) {
is Resource.Failure -> {
resultSyncLoadingShimmer.stopShimmer()
resultSyncLoadingShimmer.isVisible = false
resultSyncHolder.isVisible = false
closed = true
}
is Resource.Loading -> {
resultSyncLoadingShimmer.startShimmer()
resultSyncLoadingShimmer.isVisible = true
resultSyncHolder.isVisible = false
}
is Resource.Success -> {
resultSyncLoadingShimmer.stopShimmer()
resultSyncLoadingShimmer.isVisible = false
resultSyncHolder.isVisible = true
val d = status.value
resultSyncRating.value = d.score?.toFloat() ?: 0.0f
resultSyncCheck.setItemChecked(d.status + 1, true)
val watchedEpisodes = d.watchedEpisodes ?: 0
currentSyncProgress = watchedEpisodes
d.maxEpisodes?.let {
// don't directly call it because we don't want to override metadata observe
setSyncMaxEpisodes(it)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
resultSyncEpisodes.setProgress(watchedEpisodes * 1000, true)
} else {
resultSyncEpisodes.progress = watchedEpisodes * 1000
}
resultSyncCurrentEpisodes.text =
Editable.Factory.getInstance()?.newEditable(watchedEpisodes.toString())
normalSafeApiCall { // format might fail
context?.getString(R.string.sync_score_format)?.format(d.score ?: 0)
?.let {
resultSyncScoreText.text = it
}
}
}
null -> {
closed = false
}
}
}
binding?.resultOverlappingPanels?.setStartPanelLockState(if (closed) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
}
context?.let { ctx ->
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
/*
-1 -> None
0 -> Watching
1 -> Completed
2 -> OnHold
3 -> Dropped
4 -> PlanToWatch
5 -> ReWatching
*/
val items = listOf(
R.string.none,
R.string.type_watching,
R.string.type_completed,
R.string.type_on_hold,
R.string.type_dropped,
R.string.type_plan_to_watch,
R.string.type_re_watching
).map { ctx.getString(it) }
arrayAdapter.addAll(items)
syncBinding?.apply {
resultSyncCheck.choiceMode = AbsListView.CHOICE_MODE_SINGLE
resultSyncCheck.adapter = arrayAdapter
UIHelper.setListViewHeightBasedOnItems(resultSyncCheck)
resultSyncCheck.setOnItemClickListener { _, _, which, _ ->
syncModel.setStatus(which - 1)
}
resultSyncRating.addOnChangeListener { _, value, _ ->
syncModel.setScore(value.toInt())
}
resultSyncAddEpisode.setOnClickListener {
syncModel.setEpisodesDelta(1)
}
resultSyncSubEpisode.setOnClickListener {
syncModel.setEpisodesDelta(-1)
}
resultSyncCurrentEpisodes.doOnTextChanged { text, _, before, count ->
if (count == before) return@doOnTextChanged
text?.toString()?.toIntOrNull()?.let { ep ->
syncModel.setEpisodes(ep)
}
}
}
}
syncBinding?.resultSyncSetScore?.setOnClickListener {
syncModel.publishUserData()
}
observe(viewModel.watchStatus) { watchType ->
binding?.resultBookmarkFab?.apply {
if (watchType == WatchType.NONE) {
context?.colorFromAttribute(R.attr.white)
} else {
context?.colorFromAttribute(R.attr.colorPrimary)
}?.let {
val colorState = ColorStateList.valueOf(it)
iconTint = colorState
setTextColor(colorState)
}
setOnClickListener { fab ->
activity?.showBottomDialog(
WatchType.values().map { fab.context.getString(it.stringRes) }.toList(),
watchType.ordinal,
fab.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
viewModel.updateWatchStatus(WatchType.values()[it])
}
}
}
}
observe(viewModel.loadedLinks) { load -> observe(viewModel.loadedLinks) { load ->
if (load == null) { if (load == null) {
loadingDialog?.dismissSafe(activity) loadingDialog?.dismissSafe(activity)
@ -327,46 +549,41 @@ class ResultFragmentPhone : ResultFragment() {
} }
observeNullable(viewModel.selectedSeason) { text -> observeNullable(viewModel.selectedSeason) { text ->
result_season_button.setText(text) resultBinding?.apply {
resultSeasonButton.setText(text)
selectSeason = selectSeason =
text?.asStringNull(result_season_button?.context) text?.asStringNull(resultSeasonButton.context)
// If the season button is visible the result season button will be next focus down // If the season button is visible the result season button will be next focus down
if (result_season_button?.isVisible == true) if (resultSeasonButton.isVisible && resultResumeParent.isVisible) {
if (result_resume_parent?.isVisible == true) setFocusUpAndDown(resultResumeSeriesButton, resultSeasonButton)
setFocusUpAndDown(result_resume_series_button, result_season_button) }
//else }
// setFocusUpAndDown(result_bookmark_button, result_season_button)
} }
observeNullable(viewModel.selectedDubStatus) { status -> observeNullable(viewModel.selectedDubStatus) { status ->
result_dub_select?.setText(status) resultBinding?.apply {
resultDubSelect.setText(status)
if (result_dub_select?.isVisible == true) if (resultDubSelect.isVisible && !resultSeasonButton.isVisible && !resultEpisodeSelect.isVisible && resultResumeParent.isVisible) {
if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) { setFocusUpAndDown(resultResumeSeriesButton, resultDubSelect)
if (result_resume_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_dub_select)
//else
// setFocusUpAndDown(result_bookmark_button, result_dub_select)
} }
}
} }
observeNullable(viewModel.selectedRange) { range -> observeNullable(viewModel.selectedRange) { range ->
result_episode_select.setText(range) resultBinding?.apply {
resultEpisodeSelect.setText(range)
// If Season button is invisible then the bookmark button next focus is episode select // If Season button is invisible then the bookmark button next focus is episode select
if (result_episode_select?.isVisible == true) if (resultEpisodeSelect.isVisible && !resultSeasonButton.isVisible && resultResumeParent.isVisible) {
if (result_season_button?.isVisible != true) { setFocusUpAndDown(resultResumeSeriesButton, resultEpisodeSelect)
if (result_resume_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_episode_select)
//else
// setFocusUpAndDown(result_bookmark_button, result_episode_select)
} }
}
} }
// val preferDub = context?.getApiDubstatusSettings()?.all { it == DubStatus.Dubbed } == true // val preferDub = context?.getApiDubstatusSettings()?.all { it == DubStatus.Dubbed } == true
observe(viewModel.dubSubSelections) { range -> observe(viewModel.dubSubSelections) { range ->
result_dub_select.setOnClickListener { view -> resultBinding?.resultDubSelect?.setOnClickListener { view ->
view?.context?.let { ctx -> view?.context?.let { ctx ->
view.popupMenuNoIconsAndNoStringRes(range view.popupMenuNoIconsAndNoStringRes(range
.mapNotNull { (text, status) -> .mapNotNull { (text, status) ->
@ -382,7 +599,7 @@ class ResultFragmentPhone : ResultFragment() {
} }
observe(viewModel.rangeSelections) { range -> observe(viewModel.rangeSelections) { range ->
result_episode_select?.setOnClickListener { view -> resultBinding?.resultEpisodeSelect?.setOnClickListener { view ->
view?.context?.let { ctx -> view?.context?.let { ctx ->
val names = range val names = range
.mapNotNull { (text, r) -> .mapNotNull { (text, r) ->
@ -399,7 +616,7 @@ class ResultFragmentPhone : ResultFragment() {
} }
observe(viewModel.seasonSelections) { seasonList -> observe(viewModel.seasonSelections) { seasonList ->
result_season_button?.setOnClickListener { view -> resultBinding?.resultSeasonButton?.setOnClickListener { view ->
view?.context?.let { ctx -> view?.context?.let { ctx ->
val names = seasonList val names = seasonList
@ -433,50 +650,58 @@ class ResultFragmentPhone : ResultFragment() {
} }
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) { override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {
result_overlapping_panels?.setChildGestureRegions(gestureRegions) binding?.resultOverlappingPanels?.setChildGestureRegions(gestureRegions)
} }
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) { override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
val isInvalid = rec.isNullOrEmpty() val isInvalid = rec.isNullOrEmpty()
result_recommendations?.isGone = isInvalid
result_recommendations_btt?.isGone = isInvalid
result_recommendations_btt?.setOnClickListener {
val nextFocusDown = if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) {
result_overlapping_panels?.openEndPanel()
R.id.result_recommendations
} else {
result_overlapping_panels?.closePanels()
R.id.result_description
}
result_recommendations_btt?.nextFocusDownId = nextFocusDown
result_search?.nextFocusDownId = nextFocusDown
result_open_in_browser?.nextFocusDownId = nextFocusDown
result_share?.nextFocusDownId = nextFocusDown
}
result_overlapping_panels?.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
// very dirty selection recommendationBinding?.apply {
result_recommendations_filter_button?.isVisible = apiNames.size > 1 root.isGone = isInvalid
result_recommendations_filter_button?.text = matchAgainst root.post {
result_recommendations_filter_button?.setOnClickListener { _ -> rec?.let { list ->
activity?.showBottomDialog( (resultRecommendationsList.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst })
apiNames,
apiNames.indexOf(matchAgainst),
getString(R.string.home_change_provider_img_des), false, {}
) {
setRecommendations(rec, apiNames[it])
} }
} }
} ?: run {
result_recommendations_filter_button?.isVisible = false
} }
result_recommendations?.post { binding?.apply {
rec?.let { list -> resultRecommendationsBtt.isGone = isInvalid
(result_recommendations?.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst }) resultRecommendationsBtt.setOnClickListener {
val nextFocusDown = if (resultOverlappingPanels.getSelectedPanel().ordinal == 1) {
resultOverlappingPanels.openEndPanel()
R.id.result_recommendations
} else {
resultOverlappingPanels.closePanels()
R.id.result_description
}
resultBinding?.apply {
resultRecommendationsBtt.nextFocusDownId = nextFocusDown
resultSearch.nextFocusDownId = nextFocusDown
resultOpenInBrowser.nextFocusDownId = nextFocusDown
resultShare.nextFocusDownId = nextFocusDown
}
}
resultOverlappingPanels.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
// very dirty selection
recommendationBinding?.resultRecommendationsFilterButton?.apply {
isVisible = apiNames.size > 1
text = matchAgainst
setOnClickListener { _ ->
activity?.showBottomDialog(
apiNames,
apiNames.indexOf(matchAgainst),
getString(R.string.home_change_provider_img_des), false, {}
) {
setRecommendations(rec, apiNames[it])
}
}
}
} ?: run {
recommendationBinding?.resultRecommendationsFilterButton?.isVisible = false
} }
} }
} }

View file

@ -1,8 +1,11 @@
package com.lagradost.cloudstream3.ui.result package com.lagradost.cloudstream3.ui.result
import android.app.Dialog import android.app.Dialog
import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
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
@ -12,23 +15,43 @@ import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.databinding.FragmentResultTvBinding
import com.lagradost.cloudstream3.mvvm.Resource 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.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.utils.ExtractorLink 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.SingleSelectionHelper.showBottomDialogInstant
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 kotlinx.android.synthetic.main.fragment_result_tv.*
class ResultFragmentTv : ResultFragment() { class ResultFragmentTv : ResultFragment() {
override val resultLayout = R.layout.fragment_result_tv override val resultLayout = R.layout.fragment_result_tv
private var binding: FragmentResultTvBinding? = null
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null
binding = FragmentResultTvBinding.bind(root)
return root
}
private var currentRecommendations: List<SearchResponse> = emptyList() private var currentRecommendations: List<SearchResponse> = emptyList()
private fun handleSelection(data: Any) { private fun handleSelection(data: Any) {
@ -36,12 +59,15 @@ class ResultFragmentTv : ResultFragment() {
is EpisodeRange -> { is EpisodeRange -> {
viewModel.changeRange(data) viewModel.changeRange(data)
} }
is Int -> { is Int -> {
viewModel.changeSeason(data) viewModel.changeSeason(data)
} }
is DubStatus -> { is DubStatus -> {
viewModel.changeDubStatus(data) viewModel.changeDubStatus(data)
} }
is String -> { is String -> {
setRecommendations(currentRecommendations, data) setRecommendations(currentRecommendations, data)
} }
@ -66,57 +92,60 @@ class ResultFragmentTv : ResultFragment() {
private fun hasNoFocus(): Boolean { private fun hasNoFocus(): Boolean {
val focus = activity?.currentFocus val focus = activity?.currentFocus
if (focus == null || !focus.isVisible) return true if (focus == null || !focus.isVisible) return true
return focus == this.result_root return focus == binding?.resultRoot
} }
override fun updateEpisodes(episodes: Resource<List<ResultEpisode>>?) { override fun updateEpisodes(episodes: Resource<List<ResultEpisode>>?) {
super.updateEpisodes(episodes) super.updateEpisodes(episodes)
if (episodes is Resource.Success && hasNoFocus()) { if (episodes is Resource.Success && hasNoFocus()) {
result_episodes?.requestFocus() binding?.resultEpisodes?.requestFocus()
} }
} }
override fun updateMovie(data: Resource<Pair<UiText, ResultEpisode>>?) { override fun updateMovie(data: Resource<Pair<UiText, ResultEpisode>>?) {
super.updateMovie(data) super.updateMovie(data)
if (data is Resource.Success && hasNoFocus()) { if (data is Resource.Success && hasNoFocus()) {
result_play_movie?.requestFocus() 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
binding?.resultPlayTrailer?.apply {
result_play_trailer?.isGone = trailers.isNullOrEmpty() isGone = trailers.isNullOrEmpty()
result_play_trailer?.setOnClickListener { setOnClickListener {
if (trailers.isNullOrEmpty()) return@setOnClickListener if (trailers.isNullOrEmpty()) return@setOnClickListener
activity.navigate( activity.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance( R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
ExtractorLinkGenerator( ExtractorLinkGenerator(
trailers, trailers,
emptyList() emptyList()
)
) )
) )
) }
} }
} }
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) { override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
currentRecommendations = rec ?: emptyList() currentRecommendations = rec ?: emptyList()
val isInvalid = rec.isNullOrEmpty() val isInvalid = rec.isNullOrEmpty()
result_recommendations?.isGone = isInvalid binding?.apply {
result_recommendations_holder?.isGone = isInvalid resultRecommendationsList.isGone = isInvalid
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName resultRecommendationsHolder.isGone = isInvalid
(result_recommendations?.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst } val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
?: emptyList()) (resultRecommendationsList.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst }
?: emptyList())
rec?.map { it.apiName }?.distinct()?.let { apiNames -> rec?.map { it.apiName }?.distinct()?.let { apiNames ->
// very dirty selection // very dirty selection
result_recommendations_filter_selection?.isVisible = apiNames.size > 1 resultRecommendationsFilterSelection.isVisible = apiNames.size > 1
result_recommendations_filter_selection?.update(apiNames.map { txt(it) to it }) resultRecommendationsFilterSelection.update(apiNames.map { txt(it) to it })
result_recommendations_filter_selection?.select(apiNames.indexOf(matchAgainst)) resultRecommendationsFilterSelection.select(apiNames.indexOf(matchAgainst))
} ?: run { } ?: run {
result_recommendations_filter_selection?.isVisible = false resultRecommendationsFilterSelection.isVisible = false
}
} }
} }
@ -124,24 +153,37 @@ class ResultFragmentTv : ResultFragment() {
var popupDialog: Dialog? = null var popupDialog: Dialog? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding?.apply {
resultEpisodes.layoutManager =
LinearListLayout(resultEpisodes.context).apply {
setHorizontal()
}
result_episodes?.layoutManager = resultSeasonSelection.setAdapter()
//LinearListLayout(result_episodes ?: return, result_episodes?.context).apply { resultRangeSelection.setAdapter()
LinearListLayout(result_episodes?.context).apply { resultDubSelection.setAdapter()
setHorizontal() resultRecommendationsFilterSelection.setAdapter()
}
observe(viewModel.watchStatus) { watchType ->
binding?.resultBookmarkButton?.apply {
setText(watchType.stringRes)
setOnClickListener { view ->
activity?.showBottomDialog(
WatchType.values().map { view.context.getString(it.stringRes) }.toList(),
watchType.ordinal,
view.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
viewModel.updateWatchStatus(WatchType.values()[it])
}
}
} }
// (result_episodes?.adapter as EpisodeAdapter?)?.apply { }
// layout = R.layout.result_episode_both_tv
// }
//result_episodes?.setMaxViewPoolSize(0, Int.MAX_VALUE)
result_season_selection.setAdapter()
result_range_selection.setAdapter()
result_dub_selection.setAdapter()
result_recommendations_filter_selection.setAdapter()
observeNullable(viewModel.selectPopup) { popup -> observeNullable(viewModel.selectPopup) { popup ->
if(popup == null) { if (popup == null) {
popupDialog?.dismissSafe(activity) popupDialog?.dismissSafe(activity)
popupDialog = null popupDialog = null
return@observeNullable return@observeNullable
@ -166,67 +208,70 @@ class ResultFragmentTv : ResultFragment() {
} }
observeNullable(viewModel.loadedLinks) { load -> observeNullable(viewModel.loadedLinks) { load ->
if(load == null) { if (load == null) {
loadingDialog?.dismissSafe(activity) loadingDialog?.dismissSafe(activity)
loadingDialog = null loadingDialog = null
return@observeNullable return@observeNullable
} }
if (loadingDialog?.isShowing != true) { if (loadingDialog?.isShowing != true) {
loadingDialog?.dismissSafe(activity) loadingDialog?.dismissSafe(activity)
loadingDialog = null loadingDialog = null
} }
loadingDialog = loadingDialog ?: context?.let { ctx -> loadingDialog = loadingDialog ?: context?.let { ctx ->
val builder = BottomSheetDialog(ctx) val builder = BottomSheetDialog(ctx)
builder.setContentView(R.layout.bottom_loading) builder.setContentView(R.layout.bottom_loading)
builder.setOnDismissListener { builder.setOnDismissListener {
loadingDialog = null loadingDialog = null
viewModel.cancelLinks() viewModel.cancelLinks()
} }
//builder.setOnCancelListener { //builder.setOnCancelListener {
// it?.dismiss() // it?.dismiss()
//} //}
builder.setCanceledOnTouchOutside(true) builder.setCanceledOnTouchOutside(true)
builder.show() builder.show()
builder builder
} }
} }
observeNullable(viewModel.episodesCountText) { count -> observeNullable(viewModel.episodesCountText) { count ->
result_episodes_text.setText(count) binding?.resultEpisodesText.setText(count)
} }
observe(viewModel.selectedRangeIndex) { selected -> observe(viewModel.selectedRangeIndex) { selected ->
result_range_selection.select(selected) binding?.resultRangeSelection.select(selected)
} }
observe(viewModel.selectedSeasonIndex) { selected -> observe(viewModel.selectedSeasonIndex) { selected ->
result_season_selection.select(selected) binding?.resultSeasonSelection.select(selected)
} }
observe(viewModel.selectedDubStatusIndex) { selected -> observe(viewModel.selectedDubStatusIndex) { selected ->
result_dub_selection.select(selected) binding?.resultDubSelection.select(selected)
} }
observe(viewModel.rangeSelections) { observe(viewModel.rangeSelections) {
result_range_selection.update(it) binding?.resultRangeSelection.update(it)
} }
observe(viewModel.dubSubSelections) { observe(viewModel.dubSubSelections) {
result_dub_selection.update(it) binding?.resultDubSelection.update(it)
} }
observe(viewModel.seasonSelections) { observe(viewModel.seasonSelections) {
result_season_selection.update(it) binding?.resultSeasonSelection.update(it)
} }
result_back?.setOnClickListener { binding?.apply {
activity?.popCurrentPage() resultBack.setOnClickListener {
} activity?.popCurrentPage()
result_recommendations?.spanCount = 8
result_recommendations?.adapter =
SearchAdapter(
ArrayList(),
result_recommendations,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
} }
resultRecommendationsList.spanCount = 8
resultRecommendationsList.adapter =
SearchAdapter(
ArrayList(),
resultRecommendationsList,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
}
} }
} }

View file

@ -13,6 +13,7 @@ import androidx.core.view.isVisible
import com.discord.panels.PanelsChildGestureRegionObserver 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.*
@ -20,10 +21,9 @@ 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_swipe.*
import kotlinx.android.synthetic.main.fragment_result_tv.* import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.fragment_trailer.* import kotlinx.android.synthetic.main.fragment_trailer.*
import kotlinx.android.synthetic.main.trailer_custom_layout.*
open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreenPlayer(), open class ResultTrailerPlayer : FullScreenPlayer(),
PanelsChildGestureRegionObserver.GestureRegionsListener, IOnBackPressed { PanelsChildGestureRegionObserver.GestureRegionsListener, IOnBackPressed {
override var lockRotation = false override var lockRotation = false
@ -75,7 +75,7 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
) )
} }
player_intro_play?.apply { playerBinding?.playerIntroPlay?.apply {
layoutParams = layoutParams =
FrameLayout.LayoutParams( FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT,
@ -83,7 +83,7 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
) )
} }
if (player_intro_play?.isGone == true) { if (playerBinding?.playerIntroPlay?.isGone == true) {
result_top_holder?.apply { result_top_holder?.apply {
val anim = ValueAnimator.ofInt( val anim = ValueAnimator.ofInt(
@ -131,7 +131,8 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
private fun updateFullscreen(fullscreen: Boolean) { private fun updateFullscreen(fullscreen: Boolean) {
isFullScreenPlayer = fullscreen isFullScreenPlayer = fullscreen
lockRotation = fullscreen lockRotation = fullscreen
player_fullscreen?.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 result_top_bar?.isVisible = false
@ -157,14 +158,14 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
player_fullscreen?.setOnClickListener { playerBinding?.playerFullscreen?.setOnClickListener {
updateFullscreen(!isFullScreenPlayer) updateFullscreen(!isFullScreenPlayer)
} }
updateFullscreen(isFullScreenPlayer) updateFullscreen(isFullScreenPlayer)
uiReset() uiReset()
player_intro_play?.setOnClickListener { playerBinding?.playerIntroPlay?.setOnClickListener {
player_intro_play?.isGone = true playerBinding?.playerIntroPlay?.isGone = true
player.handleEvent(CSPlayerEvent.Play) player.handleEvent(CSPlayerEvent.Play)
updateUIVisibility() updateUIVisibility()
fixPlayerSize() fixPlayerSize()

View file

@ -277,7 +277,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"> android:descendantFocusability="blocksDescendants">
<include layout="@layout/fragment_trailer" /> <include
android:id="@+id/fragment_trailer"
layout="@layout/fragment_trailer" />
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
@ -721,7 +723,6 @@
<LinearLayout <LinearLayout
android:id="@+id/result_episodes_tab"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">

View file

@ -189,7 +189,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start"> android:layout_gravity="start">
<include layout="@layout/result_sync"/> <include layout="@layout/result_sync" android:id="@+id/result_sync"/>
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
@ -198,7 +198,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<include layout="@layout/fragment_result" /> <include layout="@layout/fragment_result" android:id="@+id/fragment_result" />
</FrameLayout> </FrameLayout>
@ -209,7 +209,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="end"> android:layout_gravity="end">
<include layout="@layout/result_recommendations" /> <include layout="@layout/result_recommendations" android:id="@+id/result_recommendations" />
</FrameLayout> </FrameLayout>
</com.discord.panels.OverlappingPanelsLayout> </com.discord.panels.OverlappingPanelsLayout>

View file

@ -746,7 +746,7 @@
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:nextFocusUp="@id/result_episodes" android:nextFocusUp="@id/result_episodes"
android:nextFocusDown="@id/result_recommendations" android:nextFocusDown="@id/result_recommendations_list"
android:orientation="horizontal" android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="2" tools:itemCount="2"
@ -763,7 +763,7 @@
</LinearLayout> </LinearLayout>
<com.lagradost.cloudstream3.ui.AutofitRecyclerView <com.lagradost.cloudstream3.ui.AutofitRecyclerView
android:id="@+id/result_recommendations" android:id="@+id/result_recommendations_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -84,6 +84,33 @@
style="@style/SmallBlackButton" style="@style/SmallBlackButton"
android:text="@string/filler" /> android:text="@string/filler" />
</FrameLayout> </FrameLayout>
<!-- atm this is useless, however it might be used for PIP one day? -->
<ImageView
android:visibility="gone"
android:id="@+id/player_fullscreen"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="20dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24"
app:tint="@color/white" />
<FrameLayout
android:id="@+id/player_intro_play"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone" />
<ImageView
android:id="@+id/player_open_source"
android:layout_width="0dp"
android:layout_height="0dp"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:visibility="gone"
android:importantForAccessibility="no" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_holder" android:id="@+id/player_video_holder"

View file

@ -167,6 +167,34 @@
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:visibility="gone"
android:id="@+id/player_intro_play"
android:layout_width="0dp"
android:layout_height="0dp" />
<ImageView
android:visibility="gone"
android:id="@+id/player_open_source"
android:layout_width="0dp"
android:layout_height="0dp"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:importantForAccessibility="no" />
<!-- atm this is useless, however it might be used for PIP one day? -->
<ImageView
android:visibility="gone"
android:id="@+id/player_fullscreen"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="20dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24"
app:tint="@color/white" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_holder" android:id="@+id/player_video_holder"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -20,7 +20,7 @@
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:layout_marginStart="0dp" android:layout_marginStart="0dp"
android:layout_marginEnd="0dp" android:layout_marginEnd="0dp"
app:layout_constraintBottom_toTopOf="@id/result_recommendations" /> app:layout_constraintBottom_toTopOf="@id/result_recommendations_list" />
<com.lagradost.cloudstream3.ui.AutofitRecyclerView <com.lagradost.cloudstream3.ui.AutofitRecyclerView
android:descendantFocusability="afterDescendants" android:descendantFocusability="afterDescendants"
@ -30,7 +30,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipToPadding="false" android:clipToPadding="false"
app:spanCount="3" app:spanCount="3"
android:id="@+id/result_recommendations" android:id="@+id/result_recommendations_list"
tools:listitem="@layout/search_result_grid" tools:listitem="@layout/search_result_grid"
android:orientation="vertical" /> android:orientation="vertical" />
</LinearLayout> </LinearLayout>

View file

@ -75,11 +75,118 @@
android:src="@drawable/play_button" /> android:src="@drawable/play_button" />
</FrameLayout> </FrameLayout>
<FrameLayout
android:visibility="gone"
android:id="@+id/player_top_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="80dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="32dp"
android:orientation="vertical">
<TextView
android:id="@+id/player_video_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
tools:text="Hello world" />
<TextView
android:id="@+id/player_video_title_rez"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="@color/white"
android:textSize="16sp"
tools:text="1920x1080" />
<FrameLayout
android:id="@+id/player_episode_filler_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="2dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/player_episode_filler"
style="@style/SmallBlackButton"
android:text="@string/filler" />
</FrameLayout>
</LinearLayout>
<!-- Removed as it has no use anymore-->
<!--<androidx.mediarouter.app.MediaRouteButton
android:id="@+id/player_media_route_button"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="end"
android:layout_margin="5dp"
android:mediaRouteTypes="user"
android:visibility="visible"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />-->
<FrameLayout
android:id="@+id/player_go_back_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:contentDescription="@string/go_back_img_des"
android:src="@drawable/ic_baseline_arrow_back_24"
app:tint="@android:color/white" />
<ImageView
android:id="@+id/player_go_back"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/go_back_img_des"
android:focusable="true" />
</FrameLayout>
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_holder" android:id="@+id/player_video_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/skip_chapter_button"
style="@style/NiceButton"
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_marginEnd="100dp"
android:backgroundTint="@color/skipOpTransparent"
android:maxLines="1"
android:padding="10dp"
android:textColor="@color/white"
android:visibility="gone"
app:cornerRadius="@dimen/rounded_button_radius"
app:layout_constraintBottom_toTopOf="@+id/bottom_player_bar"
app:layout_constraintEnd_toEndOf="parent"
app:strokeColor="@color/white"
app:strokeWidth="1dp"
tools:text="Skip Opening"
tools:visibility="visible" />
<!--use for thinner app:trackThickness="3dp" com.google.android.material.progressindicator.CircularProgressIndicator--> <!--use for thinner app:trackThickness="3dp" com.google.android.material.progressindicator.CircularProgressIndicator-->
<ProgressBar <ProgressBar
android:id="@+id/player_buffering" android:id="@+id/player_buffering"
@ -282,6 +389,11 @@
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</LinearLayout> </LinearLayout>
<FrameLayout
android:id="@+id/piphide"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView <ImageView
android:id="@+id/player_open_source" android:id="@+id/player_open_source"
android:layout_width="24dp" android:layout_width="24dp"
@ -366,7 +478,110 @@
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24" android:src="@drawable/baseline_fullscreen_24"
app:tint="@color/white" /> app:tint="@color/white" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="60dp"
android:gravity="center"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/player_lock"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_skip_episode"
android:nextFocusRight="@id/player_resize_btt"
android:text="@string/video_lock"
app:icon="@drawable/video_locked"
app:iconSize="30dp" />
<LinearLayout
android:id="@+id/player_lock_holder"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/player_resize_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_lock"
android:nextFocusRight="@id/player_speed_btt"
android:text="@string/video_aspect_ratio_resize"
app:icon="@drawable/ic_baseline_aspect_ratio_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_speed_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_resize_btt"
android:nextFocusRight="@id/player_subtitle_offset_btt"
app:icon="@drawable/ic_baseline_speed_24"
tools:text="Speed" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_subtitle_offset_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_speed_btt"
android:nextFocusRight="@id/player_sources_btt"
android:text="@string/subtitle_offset"
android:visibility="gone"
app:icon="@drawable/ic_outline_subtitles_24"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_sources_btt"
style="@style/VideoButton"
android:layout_height="40dp"
android:nextFocusLeft="@id/player_subtitle_offset_btt"
android:nextFocusRight="@id/player_tracks_btt"
android:text="@string/video_source"
app:icon="@drawable/ic_baseline_playlist_play_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_tracks_btt"
style="@style/VideoButton"
android:layout_height="40dp"
android:nextFocusLeft="@id/player_sources_btt"
android:nextFocusRight="@id/player_skip_op"
android:text="@string/tracks"
app:icon="@drawable/ic_baseline_playlist_play_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_skip_op"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_sources_btt"
android:nextFocusRight="@id/player_skip_episode"
android:text="@string/video_skip_op"
app:icon="@drawable/ic_baseline_fast_forward_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_skip_episode"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_skip_op"
android:nextFocusRight="@id/player_lock"
android:text="@string/next_episode"
app:icon="@drawable/ic_baseline_skip_next_24" />
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout <LinearLayout