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

@ -14,16 +14,15 @@ import android.provider.Settings
import android.text.Editable import android.text.Editable
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.KeyEvent import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
import android.view.animation.AlphaAnimation import android.view.animation.AlphaAnimation
import android.view.animation.Animation import android.view.animation.Animation
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.graphics.blue import androidx.core.graphics.blue
import androidx.core.graphics.green import androidx.core.graphics.green
@ -37,10 +36,13 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.keyEventListener import com.lagradost.cloudstream3.CommonActivity.keyEventListener
import com.lagradost.cloudstream3.CommonActivity.playerEventListener import com.lagradost.cloudstream3.CommonActivity.playerEventListener
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding
import com.lagradost.cloudstream3.databinding.SubtitleOffsetBinding
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
@ -52,28 +54,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.Vector2 import com.lagradost.cloudstream3.utils.Vector2
import kotlinx.android.synthetic.main.player_custom_layout.*
import kotlinx.android.synthetic.main.player_custom_layout.bottom_player_bar
import kotlinx.android.synthetic.main.player_custom_layout.exo_ffwd
import kotlinx.android.synthetic.main.player_custom_layout.exo_ffwd_text
import kotlinx.android.synthetic.main.player_custom_layout.exo_progress
import kotlinx.android.synthetic.main.player_custom_layout.exo_rew
import kotlinx.android.synthetic.main.player_custom_layout.exo_rew_text
import kotlinx.android.synthetic.main.player_custom_layout.player_center_menu
import kotlinx.android.synthetic.main.player_custom_layout.player_ffwd_holder
import kotlinx.android.synthetic.main.player_custom_layout.player_holder
import kotlinx.android.synthetic.main.player_custom_layout.player_pause_play
import kotlinx.android.synthetic.main.player_custom_layout.player_progressbar_left
import kotlinx.android.synthetic.main.player_custom_layout.player_progressbar_left_holder
import kotlinx.android.synthetic.main.player_custom_layout.player_progressbar_left_icon
import kotlinx.android.synthetic.main.player_custom_layout.player_progressbar_right
import kotlinx.android.synthetic.main.player_custom_layout.player_progressbar_right_holder
import kotlinx.android.synthetic.main.player_custom_layout.player_progressbar_right_icon
import kotlinx.android.synthetic.main.player_custom_layout.player_rew_holder
import kotlinx.android.synthetic.main.player_custom_layout.player_time_text
import kotlinx.android.synthetic.main.player_custom_layout.player_video_bar
import kotlinx.android.synthetic.main.player_custom_layout.shadow_overlay
import kotlinx.android.synthetic.main.trailer_custom_layout.*
import kotlin.math.* import kotlin.math.*
const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking
@ -92,6 +72,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
protected open var isFullScreenPlayer = true protected open var isFullScreenPlayer = true
protected open var isTv = false protected open var isTv = false
protected var playerBinding: PlayerCustomLayoutBinding? = null
// state of player UI // state of player UI
protected var isShowing = false protected var isShowing = false
protected var isLocked = false protected var isLocked = false
@ -109,6 +92,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
* This will be set in runtime based on settings. * This will be set in runtime based on settings.
**/ **/
protected var currentQualityProfile = 1 protected var currentQualityProfile = 1
// protected var currentPrefQuality = // protected var currentPrefQuality =
// Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell // Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell
protected var fastForwardTime = 10000L protected var fastForwardTime = 10000L
@ -177,6 +161,21 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
R.drawable.ic_baseline_volume_up_24, R.drawable.ic_baseline_volume_up_24,
) )
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null
playerBinding = PlayerCustomLayoutBinding.bind(root.findViewById(R.id.player_holder))
return root
}
override fun onDestroyView() {
playerBinding = null
super.onDestroyView()
}
open fun showMirrorsDialogue() { open fun showMirrorsDialogue() {
throw NotImplementedError() throw NotImplementedError()
} }
@ -209,24 +208,24 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
if (isShowing) { if (isShowing) {
updateUIVisibility() updateUIVisibility()
} else { } else {
player_holder?.postDelayed({ updateUIVisibility() }, 200) playerBinding?.playerHolder?.postDelayed({ updateUIVisibility() }, 200)
} }
val titleMove = if (isShowing) 0f else -50.toPx.toFloat() val titleMove = if (isShowing) 0f else -50.toPx.toFloat()
player_video_title?.let { playerBinding?.playerVideoTitle?.let {
ObjectAnimator.ofFloat(it, "translationY", titleMove).apply { ObjectAnimator.ofFloat(it, "translationY", titleMove).apply {
duration = 200 duration = 200
start() start()
} }
} }
player_video_title_rez?.let { playerBinding?.playerVideoTitleRez?.let {
ObjectAnimator.ofFloat(it, "translationY", titleMove).apply { ObjectAnimator.ofFloat(it, "translationY", titleMove).apply {
duration = 200 duration = 200
start() start()
} }
} }
val playerBarMove = if (isShowing) 0f else 50.toPx.toFloat() val playerBarMove = if (isShowing) 0f else 50.toPx.toFloat()
bottom_player_bar?.let { playerBinding?.bottomPlayerBar?.let {
ObjectAnimator.ofFloat(it, "translationY", playerBarMove).apply { ObjectAnimator.ofFloat(it, "translationY", playerBarMove).apply {
duration = 200 duration = 200
start() start()
@ -242,7 +241,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
val sView = subView val sView = subView
val sStyle = subStyle val sStyle = subStyle
if (sView != null && sStyle != null) { if (sView != null && sStyle != null) {
val move = if (isShowing) -((bottom_player_bar?.height?.toFloat() val move = if (isShowing) -((playerBinding?.bottomPlayerBar?.height?.toFloat()
?: 0f) + 40.toPx) else -sStyle.elevation.toPx.toFloat() ?: 0f) + 40.toPx) else -sStyle.elevation.toPx.toFloat()
ObjectAnimator.ofFloat(sView, "translationY", move).apply { ObjectAnimator.ofFloat(sView, "translationY", move).apply {
duration = 200 duration = 200
@ -251,23 +250,25 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
val playerSourceMove = if (isShowing) 0f else -50.toPx.toFloat() val playerSourceMove = if (isShowing) 0f else -50.toPx.toFloat()
player_open_source?.let {
playerBinding?.apply {
playerOpenSource.let {
ObjectAnimator.ofFloat(it, "translationY", playerSourceMove).apply { ObjectAnimator.ofFloat(it, "translationY", playerSourceMove).apply {
duration = 200 duration = 200
start() start()
} }
} }
if (!isLocked) { if (!isLocked) {
player_ffwd_holder?.alpha = 1f playerFfwdHolder.alpha = 1f
player_rew_holder?.alpha = 1f playerRewHolder.alpha = 1f
// player_pause_play_holder?.alpha = 1f // player_pause_play_holder?.alpha = 1f
shadow_overlay?.isVisible = true shadowOverlay.isVisible = true
shadow_overlay?.startAnimation(fadeAnimation) shadowOverlay.startAnimation(fadeAnimation)
player_ffwd_holder?.startAnimation(fadeAnimation) playerFfwdHolder.startAnimation(fadeAnimation)
player_rew_holder?.startAnimation(fadeAnimation) playerRewHolder.startAnimation(fadeAnimation)
player_pause_play?.startAnimation(fadeAnimation) playerPausePlay.startAnimation(fadeAnimation)
/*if (isBuffering) { /*if (isBuffering) {
player_pause_play?.isVisible = false player_pause_play?.isVisible = false
@ -280,13 +281,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
//player_buffering?.startAnimation(fadeAnimation) //player_buffering?.startAnimation(fadeAnimation)
} }
bottom_player_bar?.startAnimation(fadeAnimation) bottomPlayerBar.startAnimation(fadeAnimation)
player_open_source?.startAnimation(fadeAnimation) playerOpenSource.startAnimation(fadeAnimation)
player_top_holder?.startAnimation(fadeAnimation) playerTopHolder.startAnimation(fadeAnimation)
}
} }
override fun subtitlesChanged() { override fun subtitlesChanged() {
player_subtitle_offset_btt?.isGone = player.getCurrentPreferredSubtitle() == null playerBinding?.playerSubtitleOffsetBtt?.isGone =
player.getCurrentPreferredSubtitle() == null
} }
protected fun enterFullscreen() { protected fun enterFullscreen() {
@ -332,7 +335,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
private fun setPlayBackSpeed(speed: Float) { private fun setPlayBackSpeed(speed: Float) {
try { try {
setKey(PLAYBACK_SPEED_KEY, speed) setKey(PLAYBACK_SPEED_KEY, speed)
player_speed_btt?.text = playerBinding?.playerSpeedBtt?.text =
getString(R.string.player_speed_text_format).format(speed) getString(R.string.player_speed_text_format).format(speed)
.replace(".0x", "x") .replace(".0x", "x")
} catch (e: Exception) { } catch (e: Exception) {
@ -348,67 +351,68 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
private fun showSubtitleOffsetDialog() { private fun showSubtitleOffsetDialog() {
context?.let { ctx -> val ctx = context ?: return
val binding = SubtitleOffsetBinding.inflate(LayoutInflater.from(ctx), null, false)
val builder = val builder =
AlertDialog.Builder(ctx, R.style.AlertDialogCustom) AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
.setView(R.layout.subtitle_offset) .setView(binding.root)
val dialog = builder.create() val dialog = builder.create()
dialog.show() dialog.show()
val beforeOffset = subtitleDelay val beforeOffset = subtitleDelay
val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!! /*val applyButton = dialog.findViewById<TextView>(R.id.apply_btt)!!
val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!! val cancelButton = dialog.findViewById<TextView>(R.id.cancel_btt)!!
val input = dialog.findViewById<EditText>(R.id.subtitle_offset_input)!! val input = dialog.findViewById<EditText>(R.id.subtitle_offset_input)!!
val sub = dialog.findViewById<ImageView>(R.id.subtitle_offset_subtract)!! val sub = dialog.findViewById<ImageView>(R.id.subtitle_offset_subtract)!!
val subMore = dialog.findViewById<ImageView>(R.id.subtitle_offset_subtract_more)!! val subMore = dialog.findViewById<ImageView>(R.id.subtitle_offset_subtract_more)!!
val add = dialog.findViewById<ImageView>(R.id.subtitle_offset_add)!! val add = dialog.findViewById<ImageView>(R.id.subtitle_offset_add)!!
val addMore = dialog.findViewById<ImageView>(R.id.subtitle_offset_add_more)!! val addMore = dialog.findViewById<ImageView>(R.id.subtitle_offset_add_more)!!
val subTitle = dialog.findViewById<TextView>(R.id.subtitle_offset_sub_title)!! val subTitle = dialog.findViewById<TextView>(R.id.subtitle_offset_sub_title)!!*/
binding.apply {
subtitleOffsetInput.doOnTextChanged { text, _, _, _ ->
text?.toString()?.toLongOrNull()?.let { time ->
subtitleDelay = time
val str = when {
time > 0L -> {
txt(R.string.subtitle_offset_extra_hint_later_format, time)
}
input.doOnTextChanged { text, _, _, _ -> time < 0L -> {
text?.toString()?.toLongOrNull()?.let { txt(R.string.subtitle_offset_extra_hint_before_format, -time)
subtitleDelay = it
when {
it > 0L -> {
context?.getString(R.string.subtitle_offset_extra_hint_later_format)
?.format(it)
}
it < 0L -> {
context?.getString(R.string.subtitle_offset_extra_hint_before_format)
?.format(-it)
}
it == 0L -> {
context?.getString(R.string.subtitle_offset_extra_hint_none_format)
} }
else -> { else -> {
null txt(R.string.subtitle_offset_extra_hint_none_format)
}
}?.let { str ->
subTitle.text = str
} }
} }
subtitleOffsetSubTitle.setText(str)
} }
input.text = Editable.Factory.getInstance()?.newEditable(beforeOffset.toString()) }
subtitleOffsetInput.text =
Editable.Factory.getInstance()?.newEditable(beforeOffset.toString())
val buttonChange = 100L val buttonChange = 100L
val buttonChangeMore = 1000L val buttonChangeMore = 1000L
fun changeBy(by: Long) { fun changeBy(by: Long) {
val current = (input.text?.toString()?.toLongOrNull() ?: 0) + by val current = (subtitleOffsetInput.text?.toString()?.toLongOrNull() ?: 0) + by
input.text = Editable.Factory.getInstance()?.newEditable(current.toString()) subtitleOffsetInput.text =
Editable.Factory.getInstance()?.newEditable(current.toString())
} }
add.setOnClickListener { subtitleOffsetAdd.setOnClickListener {
changeBy(buttonChange) changeBy(buttonChange)
} }
addMore.setOnClickListener { subtitleOffsetAddMore.setOnClickListener {
changeBy(buttonChangeMore) changeBy(buttonChangeMore)
} }
sub.setOnClickListener { subtitleOffsetSubtract.setOnClickListener {
changeBy(-buttonChange) changeBy(-buttonChange)
} }
subMore.setOnClickListener { subtitleOffsetSubtractMore.setOnClickListener {
changeBy(-buttonChangeMore) changeBy(-buttonChangeMore)
} }
@ -416,17 +420,18 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
if (isFullScreenPlayer) if (isFullScreenPlayer)
activity?.hideSystemUI() activity?.hideSystemUI()
} }
applyButton.setOnClickListener { applyBtt.setOnClickListener {
dialog.dismissSafe(activity) dialog.dismissSafe(activity)
player.seekTime(1L) player.seekTime(1L)
} }
cancelButton.setOnClickListener { cancelBtt.setOnClickListener {
subtitleDelay = beforeOffset subtitleDelay = beforeOffset
dialog.dismissSafe(activity) dialog.dismissSafe(activity)
} }
} }
} }
private fun showSpeedDialog() { private fun showSpeedDialog() {
val speedsText = val speedsText =
listOf( listOf(
@ -463,22 +468,23 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
fun resetRewindText() { fun resetRewindText() {
exo_rew_text?.text = playerBinding?.exoRewText?.text =
getString(R.string.rew_text_regular_format).format(fastForwardTime / 1000) getString(R.string.rew_text_regular_format).format(fastForwardTime / 1000)
} }
fun resetFastForwardText() { fun resetFastForwardText() {
exo_ffwd_text?.text = playerBinding?.exoFfwdText?.text =
getString(R.string.ffw_text_regular_format).format(fastForwardTime / 1000) getString(R.string.ffw_text_regular_format).format(fastForwardTime / 1000)
} }
private fun rewind() { private fun rewind() {
try { try {
player_center_menu?.isGone = false playerBinding?.apply {
player_rew_holder?.alpha = 1f playerCenterMenu.isGone = false
playerRewHolder.alpha = 1f
val rotateLeft = AnimationUtils.loadAnimation(context, R.anim.rotate_left) val rotateLeft = AnimationUtils.loadAnimation(context, R.anim.rotate_left)
exo_rew?.startAnimation(rotateLeft) exoRew.startAnimation(rotateLeft)
val goLeft = AnimationUtils.loadAnimation(context, R.anim.go_left) val goLeft = AnimationUtils.loadAnimation(context, R.anim.go_left)
goLeft.setAnimationListener(object : Animation.AnimationListener { goLeft.setAnimationListener(object : Animation.AnimationListener {
@ -487,15 +493,17 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
override fun onAnimationRepeat(animation: Animation?) {} override fun onAnimationRepeat(animation: Animation?) {}
override fun onAnimationEnd(animation: Animation?) { override fun onAnimationEnd(animation: Animation?) {
exo_rew_text?.post { exoRewText.post {
resetRewindText() resetRewindText()
player_center_menu?.isGone = !isShowing playerCenterMenu.isGone = !isShowing
player_rew_holder?.alpha = if (isShowing) 1f else 0f playerRewHolder.alpha = if (isShowing) 1f else 0f
} }
} }
}) })
exo_rew_text?.startAnimation(goLeft) exoRewText.startAnimation(goLeft)
exo_rew_text?.text = getString(R.string.rew_text_format).format(fastForwardTime / 1000) exoRewText.text =
getString(R.string.rew_text_format).format(fastForwardTime / 1000)
}
player.seekTime(-fastForwardTime) player.seekTime(-fastForwardTime)
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
@ -504,11 +512,12 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
private fun fastForward() { private fun fastForward() {
try { try {
player_center_menu?.isGone = false playerBinding?.apply {
player_ffwd_holder?.alpha = 1f playerCenterMenu.isGone = false
playerFfwdHolder.alpha = 1f
val rotateRight = AnimationUtils.loadAnimation(context, R.anim.rotate_right) val rotateRight = AnimationUtils.loadAnimation(context, R.anim.rotate_right)
exo_ffwd?.startAnimation(rotateRight) exoFfwd.startAnimation(rotateRight)
val goRight = AnimationUtils.loadAnimation(context, R.anim.go_right) val goRight = AnimationUtils.loadAnimation(context, R.anim.go_right)
goRight.setAnimationListener(object : Animation.AnimationListener { goRight.setAnimationListener(object : Animation.AnimationListener {
@ -517,15 +526,17 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
override fun onAnimationRepeat(animation: Animation?) {} override fun onAnimationRepeat(animation: Animation?) {}
override fun onAnimationEnd(animation: Animation?) { override fun onAnimationEnd(animation: Animation?) {
exo_ffwd_text?.post { exoFfwdText.post {
resetFastForwardText() resetFastForwardText()
player_center_menu?.isGone = !isShowing playerCenterMenu.isGone = !isShowing
player_ffwd_holder?.alpha = if (isShowing) 1f else 0f playerFfwdHolder.alpha = if (isShowing) 1f else 0f
} }
} }
}) })
exo_ffwd_text?.startAnimation(goRight) exoFfwdText.startAnimation(goRight)
exo_ffwd_text?.text = getString(R.string.ffw_text_format).format(fastForwardTime / 1000) exoFfwdText.text =
getString(R.string.ffw_text_format).format(fastForwardTime / 1000)
}
player.seekTime(fastForwardTime) player.seekTime(fastForwardTime)
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
@ -535,13 +546,13 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
private fun onClickChange() { private fun onClickChange() {
isShowing = !isShowing isShowing = !isShowing
if (isShowing) { if (isShowing) {
player_intro_play?.isGone = true playerBinding?.playerIntroPlay?.isGone = true
autoHide() autoHide()
} }
if (isFullScreenPlayer) if (isFullScreenPlayer)
activity?.hideSystemUI() activity?.hideSystemUI()
animateLayoutChanges() animateLayoutChanges()
player_pause_play?.requestFocus() playerBinding?.playerPausePlay?.requestFocus()
} }
private fun toggleLock() { private fun toggleLock() {
@ -551,7 +562,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
isLocked = !isLocked isLocked = !isLocked
if (isLocked && isShowing) { if (isLocked && isShowing) {
player_holder?.postDelayed({ playerBinding?.playerHolder?.postDelayed({
if (isLocked && isShowing) { if (isLocked && isShowing) {
onClickChange() onClickChange()
} }
@ -559,8 +570,8 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
val fadeTo = if (isLocked) 0f else 1f val fadeTo = if (isLocked) 0f else 1f
playerBinding?.apply {
val fadeAnimation = AlphaAnimation(player_video_title.alpha, fadeTo).apply { val fadeAnimation = AlphaAnimation(playerVideoTitle.alpha, fadeTo).apply {
duration = 100 duration = 100
fillAfter = true fillAfter = true
} }
@ -568,9 +579,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
updateUIVisibility() updateUIVisibility()
// MENUS // MENUS
//centerMenu.startAnimation(fadeAnimation) //centerMenu.startAnimation(fadeAnimation)
player_pause_play?.startAnimation(fadeAnimation) playerPausePlay.startAnimation(fadeAnimation)
player_ffwd_holder?.startAnimation(fadeAnimation) playerFfwdHolder.startAnimation(fadeAnimation)
player_rew_holder?.startAnimation(fadeAnimation) playerRewHolder.startAnimation(fadeAnimation)
//if (hasEpisodes) //if (hasEpisodes)
// player_episodes_button?.startAnimation(fadeAnimation) // player_episodes_button?.startAnimation(fadeAnimation)
@ -578,17 +589,17 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
//video_bar.startAnimation(fadeAnimation) //video_bar.startAnimation(fadeAnimation)
//TITLE //TITLE
player_video_title_rez?.startAnimation(fadeAnimation) playerVideoTitleRez.startAnimation(fadeAnimation)
player_episode_filler?.startAnimation(fadeAnimation) playerEpisodeFiller.startAnimation(fadeAnimation)
player_video_title?.startAnimation(fadeAnimation) playerVideoTitle.startAnimation(fadeAnimation)
player_top_holder?.startAnimation(fadeAnimation) playerTopHolder.startAnimation(fadeAnimation)
// BOTTOM // BOTTOM
player_lock_holder?.startAnimation(fadeAnimation) playerLockHolder.startAnimation(fadeAnimation)
//player_go_back_holder?.startAnimation(fadeAnimation) //player_go_back_holder?.startAnimation(fadeAnimation)
shadow_overlay?.isVisible = true shadowOverlay.isVisible = true
shadow_overlay?.startAnimation(fadeAnimation) shadowOverlay.startAnimation(fadeAnimation)
}
updateLockUI() updateLockUI()
} }
@ -602,43 +613,48 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
togglePlayerTitleGone = true togglePlayerTitleGone = true
} }
} }
player_lock_holder?.isGone = isGone playerBinding?.apply {
player_video_bar?.isGone = isGone
player_pause_play?.isGone = isGone playerLockHolder.isGone = isGone
playerVideoBar.isGone = isGone
playerPausePlay.isGone = isGone
//player_buffering?.isGone = isGone //player_buffering?.isGone = isGone
player_top_holder?.isGone = isGone playerTopHolder.isGone = isGone
//player_episodes_button?.isVisible = !isGone && hasEpisodes //player_episodes_button?.isVisible = !isGone && hasEpisodes
player_video_title?.isGone = togglePlayerTitleGone playerVideoTitle.isGone = togglePlayerTitleGone
// player_video_title_rez?.isGone = isGone // player_video_title_rez?.isGone = isGone
player_episode_filler?.isGone = isGone playerEpisodeFiller.isGone = isGone
player_center_menu?.isGone = isGone playerCenterMenu.isGone = isGone
player_lock?.isGone = !isShowing playerLock.isGone = !isShowing
//player_media_route_button?.isClickable = !isGone //player_media_route_button?.isClickable = !isGone
player_go_back_holder?.isGone = isGone playerGoBackHolder.isGone = isGone
player_sources_btt?.isGone = isGone playerSourcesBtt.isGone = isGone
player_skip_episode?.isClickable = !isGone playerSkipEpisode.isClickable = !isGone
}
} }
private fun updateLockUI() { private fun updateLockUI() {
player_lock?.setIconResource(if (isLocked) R.drawable.video_locked else R.drawable.video_unlocked) playerBinding?.apply {
playerLock.setIconResource(if (isLocked) R.drawable.video_locked else R.drawable.video_unlocked)
if (layout == R.layout.fragment_player) { if (layout == R.layout.fragment_player) {
val color = if (isLocked) context?.colorFromAttribute(R.attr.colorPrimary) val color = if (isLocked) context?.colorFromAttribute(R.attr.colorPrimary)
else Color.WHITE else Color.WHITE
if (color != null) { if (color != null) {
player_lock?.setTextColor(color) playerLock.setTextColor(color)
player_lock?.iconTint = ColorStateList.valueOf(color) playerLock.iconTint = ColorStateList.valueOf(color)
player_lock?.rippleColor = playerLock.rippleColor =
ColorStateList.valueOf(Color.argb(50, color.red, color.green, color.blue)) ColorStateList.valueOf(Color.argb(50, color.red, color.green, color.blue))
} }
} }
} }
}
private var currentTapIndex = 0 private var currentTapIndex = 0
protected fun autoHide() { protected fun autoHide() {
currentTapIndex++ currentTapIndex++
val index = currentTapIndex val index = currentTapIndex
player_holder?.postDelayed({ playerBinding?.playerHolder?.postDelayed({
if (!isCurrentTouchValid && isShowing && index == currentTapIndex && player.getIsPlaying()) { if (!isCurrentTouchValid && isShowing && index == currentTapIndex && player.getIsPlaying()) {
onClickChange() onClickChange()
} }
@ -650,7 +666,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
private fun toggleShowDelayed() { private fun toggleShowDelayed() {
if (doubleTapEnabled || doubleTapPauseEnabled) { if (doubleTapEnabled || doubleTapPauseEnabled) {
val index = currentDoubleTapIndex val index = currentDoubleTapIndex
player_holder?.postDelayed({ playerBinding?.playerHolder?.postDelayed({
if (index == currentDoubleTapIndex) { if (index == currentDoubleTapIndex) {
onClickChange() onClickChange()
} }
@ -781,7 +797,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
if (event == null || view == null) return false if (event == null || view == null) return false
val currentTouch = Vector2(event.x, event.y) val currentTouch = Vector2(event.x, event.y)
val startTouch = currentTouchStart val startTouch = currentTouchStart
player_intro_play?.isGone = true
playerBinding?.apply {
playerIntroPlay.isGone = true
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
// validates if the touch is inside of the player area // validates if the touch is inside of the player area
@ -807,13 +826,18 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
} }
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
if (isCurrentTouchValid && !isLocked && isFullScreenPlayer) { if (isCurrentTouchValid && !isLocked && isFullScreenPlayer) {
// seek time // seek time
if (swipeHorizontalEnabled && currentTouchAction == TouchAction.Time) { if (swipeHorizontalEnabled && currentTouchAction == TouchAction.Time) {
val startTime = currentTouchStartPlayerTime val startTime = currentTouchStartPlayerTime
if (startTime != null) { if (startTime != null) {
calculateNewTime(startTime, startTouch, currentTouch)?.let { seekTo -> calculateNewTime(
startTime,
startTouch,
currentTouch
)?.let { seekTo ->
if (abs(seekTo - startTime) > MINIMUM_SEEK_TIME) { if (abs(seekTo - startTime) > MINIMUM_SEEK_TIME) {
player.seekTo(seekTo) player.seekTo(seekTo)
} }
@ -843,10 +867,12 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
if (doubleTapEnabled) if (doubleTapEnabled)
rewind() rewind()
} }
currentTouch.x > screenWidth / 2 + (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> { currentTouch.x > screenWidth / 2 + (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> {
if (doubleTapEnabled) if (doubleTapEnabled)
fastForward() fastForward()
} }
else -> { else -> {
player.handleEvent(CSPlayerEvent.PlayPauseToggle) player.handleEvent(CSPlayerEvent.PlayPauseToggle)
} }
@ -882,11 +908,13 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
currentTouchStartTime = null currentTouchStartTime = null
// resets UI // resets UI
player_time_text?.isVisible = false playerTimeText.isVisible = false
player_progressbar_left_holder?.isVisible = false playerProgressbarLeftHolder.isVisible = false
player_progressbar_right_holder?.isVisible = false playerProgressbarRightHolder.isVisible = false
currentLastTouchEndTime = System.currentTimeMillis() currentLastTouchEndTime = System.currentTimeMillis()
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
// if current touch is valid // if current touch is valid
if (startTouch != null && isCurrentTouchValid && !isLocked && isFullScreenPlayer) { if (startTouch != null && isCurrentTouchValid && !isLocked && isFullScreenPlayer) {
@ -925,9 +953,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
diffFromLast.y * VERTICAL_MULTIPLIER / screenHeight.toFloat() diffFromLast.y * VERTICAL_MULTIPLIER / screenHeight.toFloat()
// update UI // update UI
player_time_text?.isVisible = false playerTimeText.isVisible = false
player_progressbar_left_holder?.isVisible = false playerProgressbarLeftHolder.isVisible = false
player_progressbar_right_holder?.isVisible = false playerProgressbarRightHolder.isVisible = false
when (currentTouchAction) { when (currentTouchAction) {
TouchAction.Time -> { TouchAction.Time -> {
@ -942,16 +970,19 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
currentTouch currentTouch
)?.let { newMs -> )?.let { newMs ->
val skipMs = newMs - startTime val skipMs = newMs - startTime
player_time_text?.text = playerTimeText.apply {
text =
"${convertTimeToString(newMs / 1000)} [${ "${convertTimeToString(newMs / 1000)} [${
(if (abs(skipMs) < 1000) "" else (if (skipMs > 0) "+" else "-")) (if (abs(skipMs) < 1000) "" else (if (skipMs > 0) "+" else "-"))
}${convertTimeToString(abs(skipMs / 1000))}]" }${convertTimeToString(abs(skipMs / 1000))}]"
player_time_text?.isVisible = true isVisible = true
} }
} }
} }
}
TouchAction.Brightness -> { TouchAction.Brightness -> {
player_progressbar_right_holder?.isVisible = true playerProgressbarRightHolder.isVisible = true
val lastRequested = currentRequestedBrightness val lastRequested = currentRequestedBrightness
currentRequestedBrightness = currentRequestedBrightness =
min( min(
@ -964,11 +995,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
setBrightness(currentRequestedBrightness) setBrightness(currentRequestedBrightness)
// max is set high to make it smooth // max is set high to make it smooth
player_progressbar_right?.max = 100_000 playerProgressbarRight.max = 100_000
player_progressbar_right?.progress = playerProgressbarRight.progress =
max(2_000, (currentRequestedBrightness * 100_000f).toInt()) max(2_000, (currentRequestedBrightness * 100_000f).toInt())
player_progressbar_right_icon?.setImageResource( playerProgressbarRightIcon.setImageResource(
brightnessIcons[min( // clamp the value just in case brightnessIcons[min( // clamp the value just in case
brightnessIcons.size - 1, brightnessIcons.size - 1,
max( max(
@ -978,9 +1009,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
)] )]
) )
} }
TouchAction.Volume -> { TouchAction.Volume -> {
(activity?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.let { audioManager -> (activity?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.let { audioManager ->
player_progressbar_left_holder?.isVisible = true playerProgressbarLeftHolder.isVisible = true
val maxVolume = val maxVolume =
audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val currentVolume = val currentVolume =
@ -994,11 +1026,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
) )
// max is set high to make it smooth // max is set high to make it smooth
player_progressbar_left?.max = 100_000 playerProgressbarLeft.max = 100_000
player_progressbar_left?.progress = playerProgressbarLeft.progress =
max(2_000, (currentRequestedVolume * 100_000f).toInt()) max(2_000, (currentRequestedVolume * 100_000f).toInt())
player_progressbar_left_icon?.setImageResource( playerProgressbarLeftIcon.setImageResource(
volumeIcons[min( // clamp the value just in case volumeIcons[min( // clamp the value just in case
volumeIcons.size - 1, volumeIcons.size - 1,
max( max(
@ -1023,12 +1055,14 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
} }
} }
else -> Unit else -> Unit
} }
} }
} }
} }
} }
}
currentTouchLast = currentTouch currentTouchLast = currentTouch
return true return true
} }
@ -1048,26 +1082,29 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
return true return true
} }
} }
KeyEvent.KEYCODE_DPAD_UP -> { KeyEvent.KEYCODE_DPAD_UP -> {
if (!isShowing) { if (!isShowing) {
onClickChange() onClickChange()
return true return true
} }
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
if (!isShowing && !isLocked) { if (!isShowing && !isLocked) {
player.seekTime(-androidTVInterfaceOffSeekTime) player.seekTime(-androidTVInterfaceOffSeekTime)
return true return true
} else if (player_pause_play?.isFocused == true) { } else if (playerBinding?.playerPausePlay?.isFocused == true) {
player.seekTime(-androidTVInterfaceOnSeekTime) player.seekTime(-androidTVInterfaceOnSeekTime)
return true return true
} }
} }
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> {
if (!isShowing && !isLocked) { if (!isShowing && !isLocked) {
player.seekTime(androidTVInterfaceOffSeekTime) player.seekTime(androidTVInterfaceOffSeekTime)
return true return true
} else if (player_pause_play?.isFocused == true) { } else if (playerBinding?.playerPausePlay?.isFocused == true) {
player.seekTime(androidTVInterfaceOnSeekTime) player.seekTime(androidTVInterfaceOnSeekTime)
return true return true
} }
@ -1110,11 +1147,12 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
isShowing = false isShowing = false
// if nothing has loaded these buttons should not be visible // if nothing has loaded these buttons should not be visible
player_skip_episode?.isVisible = false playerBinding?.apply {
player_tracks_btt?.isVisible = false playerSkipEpisode.isVisible = false
player_skip_op?.isVisible = false playerTracksBtt.isVisible = false
shadow_overlay?.isVisible = false playerSkipOp.isVisible = false
shadowOverlay.isVisible = false
}
updateLockUI() updateLockUI()
updateUIVisibility() updateUIVisibility()
animateLayoutChanges() animateLayoutChanges()
@ -1143,50 +1181,65 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
PlayerEventType.Lock -> { PlayerEventType.Lock -> {
toggleLock() toggleLock()
} }
PlayerEventType.NextEpisode -> { PlayerEventType.NextEpisode -> {
player.handleEvent(CSPlayerEvent.NextEpisode) player.handleEvent(CSPlayerEvent.NextEpisode)
} }
PlayerEventType.Pause -> { PlayerEventType.Pause -> {
player.handleEvent(CSPlayerEvent.Pause) player.handleEvent(CSPlayerEvent.Pause)
} }
PlayerEventType.PlayPauseToggle -> { PlayerEventType.PlayPauseToggle -> {
player.handleEvent(CSPlayerEvent.PlayPauseToggle) player.handleEvent(CSPlayerEvent.PlayPauseToggle)
} }
PlayerEventType.Play -> { PlayerEventType.Play -> {
player.handleEvent(CSPlayerEvent.Play) player.handleEvent(CSPlayerEvent.Play)
} }
PlayerEventType.SkipCurrentChapter -> { PlayerEventType.SkipCurrentChapter -> {
player.handleEvent(CSPlayerEvent.SkipCurrentChapter) player.handleEvent(CSPlayerEvent.SkipCurrentChapter)
} }
PlayerEventType.Resize -> { PlayerEventType.Resize -> {
nextResize() nextResize()
} }
PlayerEventType.PrevEpisode -> { PlayerEventType.PrevEpisode -> {
player.handleEvent(CSPlayerEvent.PrevEpisode) player.handleEvent(CSPlayerEvent.PrevEpisode)
} }
PlayerEventType.SeekForward -> { PlayerEventType.SeekForward -> {
player.handleEvent(CSPlayerEvent.SeekForward) player.handleEvent(CSPlayerEvent.SeekForward)
} }
PlayerEventType.ShowSpeed -> { PlayerEventType.ShowSpeed -> {
showSpeedDialog() showSpeedDialog()
} }
PlayerEventType.SeekBack -> { PlayerEventType.SeekBack -> {
player.handleEvent(CSPlayerEvent.SeekBack) player.handleEvent(CSPlayerEvent.SeekBack)
} }
PlayerEventType.ToggleMute -> { PlayerEventType.ToggleMute -> {
player.handleEvent(CSPlayerEvent.ToggleMute) player.handleEvent(CSPlayerEvent.ToggleMute)
} }
PlayerEventType.ToggleHide -> { PlayerEventType.ToggleHide -> {
onClickChange() onClickChange()
} }
PlayerEventType.ShowMirrors -> { PlayerEventType.ShowMirrors -> {
showMirrorsDialogue() showMirrorsDialogue()
} }
PlayerEventType.SearchSubtitlesOnline -> { PlayerEventType.SearchSubtitlesOnline -> {
if (subsProvidersIsActive) { if (subsProvidersIsActive) {
openOnlineSubPicker(view.context, null) {} openOnlineSubPicker(view.context, null) {}
} }
} }
PlayerEventType.SkipOp -> { PlayerEventType.SkipOp -> {
skipOp() skipOp()
} }
@ -1281,94 +1334,98 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
// useSystemBrightness = // useSystemBrightness =
// settingsManager.getBoolean(ctx.getString(R.string.use_system_brightness_key), false) // settingsManager.getBoolean(ctx.getString(R.string.use_system_brightness_key), false)
} }
playerBinding?.apply {
player_speed_btt?.isVisible = playBackSpeedEnabled playerSpeedBtt.isVisible = playBackSpeedEnabled
player_resize_btt?.isVisible = playerResizeEnabled playerResizeBtt.isVisible = playerResizeEnabled
}
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} }
playerBinding?.apply {
player_pause_play?.setOnClickListener { playerPausePlay.setOnClickListener {
autoHide() autoHide()
player.handleEvent(CSPlayerEvent.PlayPauseToggle) player.handleEvent(CSPlayerEvent.PlayPauseToggle)
} }
skip_chapter_button?.setOnClickListener { skipChapterButton.setOnClickListener {
player.handleEvent(CSPlayerEvent.SkipCurrentChapter) player.handleEvent(CSPlayerEvent.SkipCurrentChapter)
} }
// init clicks // init clicks
player_resize_btt?.setOnClickListener { playerResizeBtt.setOnClickListener {
autoHide() autoHide()
nextResize() nextResize()
} }
player_speed_btt?.setOnClickListener { playerSpeedBtt.setOnClickListener {
autoHide() autoHide()
showSpeedDialog() showSpeedDialog()
} }
player_skip_op?.setOnClickListener { playerSkipOp.setOnClickListener {
autoHide() autoHide()
skipOp() skipOp()
} }
player_skip_episode?.setOnClickListener { playerSkipEpisode.setOnClickListener {
autoHide() autoHide()
player.handleEvent(CSPlayerEvent.NextEpisode) player.handleEvent(CSPlayerEvent.NextEpisode)
} }
player_lock?.setOnClickListener { playerLock.setOnClickListener {
autoHide() autoHide()
toggleLock() toggleLock()
} }
player_subtitle_offset_btt?.setOnClickListener { playerSubtitleOffsetBtt.setOnClickListener {
showSubtitleOffsetDialog() showSubtitleOffsetDialog()
} }
exo_rew?.setOnClickListener { exoRew.setOnClickListener {
autoHide() autoHide()
rewind() rewind()
} }
exo_ffwd?.setOnClickListener { exoFfwd.setOnClickListener {
autoHide() autoHide()
fastForward() fastForward()
} }
player_go_back?.setOnClickListener { playerGoBack.setOnClickListener {
activity?.popCurrentPage() activity?.popCurrentPage()
} }
player_sources_btt?.setOnClickListener { playerSourcesBtt.setOnClickListener {
showMirrorsDialogue() showMirrorsDialogue()
} }
player_tracks_btt?.setOnClickListener { playerTracksBtt.setOnClickListener {
showTracksDialogue() showTracksDialogue()
} }
// it is !not! a bug that you cant touch the right side, it does not register inputs on navbar or status bar // it is !not! a bug that you cant touch the right side, it does not register inputs on navbar or status bar
player_holder?.setOnTouchListener { callView, event -> playerHolder.setOnTouchListener { callView, event ->
return@setOnTouchListener handleMotionEvent(callView, event) return@setOnTouchListener handleMotionEvent(callView, event)
} }
exo_progress?.setOnTouchListener { _, event -> exoProgress.setOnTouchListener { _, event ->
// this makes the bar not disappear when sliding // this makes the bar not disappear when sliding
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
currentTapIndex++ currentTapIndex++
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
currentTapIndex++ currentTapIndex++
} }
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_BUTTON_RELEASE -> { MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_BUTTON_RELEASE -> {
autoHide() autoHide()
} }
} }
return@setOnTouchListener false return@setOnTouchListener false
} }
}
// init UI // init UI
try { try {
uiReset() uiReset()

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,7 +355,8 @@ open class ResultFragment : ResultTrailerPlayer() {
return@setOnLongClickListener true return@setOnLongClickListener true
} }
val show = viewModel.currentRepo?.api?.hasDownloadSupport == true && !isTvSettings() val show =
viewModel.currentRepo?.api?.hasDownloadSupport == true && !isTvSettings()
if (show) { if (show) {
download_button?.setDefaultClickListener( download_button?.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached( VideoDownloadHelper.DownloadEpisodeCached(
@ -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,10 +125,11 @@ 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
resultPosterBackgroundHolder.apply {
val fadeIn: Animation = AlphaAnimation(alpha, if (turnVis) 1.0f else 0.0f).apply { val fadeIn: Animation = AlphaAnimation(alpha, if (turnVis) 1.0f else 0.0f).apply {
interpolator = DecelerateInterpolator() interpolator = DecelerateInterpolator()
duration = 200 duration = 200
@ -109,6 +139,16 @@ class ResultFragmentPhone : ResultFragment() {
startAnimation(fadeIn) 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)
result_recommendations?.spanCount = 3 recommendationBinding?.resultRecommendationsList?.apply {
result_recommendations?.adapter = spanCount = 3
adapter =
SearchAdapter( SearchAdapter(
ArrayList(), ArrayList(),
result_recommendations, this,
) { callback -> ) { callback ->
SearchHelper.handleSearchClickCallback(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,35 +650,47 @@ 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 val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
result_recommendations_btt?.isGone = isInvalid
result_recommendations_btt?.setOnClickListener { recommendationBinding?.apply {
val nextFocusDown = if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) { root.isGone = isInvalid
result_overlapping_panels?.openEndPanel() root.post {
rec?.let { list ->
(resultRecommendationsList.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst })
}
}
}
binding?.apply {
resultRecommendationsBtt.isGone = isInvalid
resultRecommendationsBtt.setOnClickListener {
val nextFocusDown = if (resultOverlappingPanels.getSelectedPanel().ordinal == 1) {
resultOverlappingPanels.openEndPanel()
R.id.result_recommendations R.id.result_recommendations
} else { } else {
result_overlapping_panels?.closePanels() resultOverlappingPanels.closePanels()
R.id.result_description R.id.result_description
} }
resultBinding?.apply {
result_recommendations_btt?.nextFocusDownId = nextFocusDown resultRecommendationsBtt.nextFocusDownId = nextFocusDown
result_search?.nextFocusDownId = nextFocusDown resultSearch.nextFocusDownId = nextFocusDown
result_open_in_browser?.nextFocusDownId = nextFocusDown resultOpenInBrowser.nextFocusDownId = nextFocusDown
result_share?.nextFocusDownId = nextFocusDown resultShare.nextFocusDownId = nextFocusDown
} }
result_overlapping_panels?.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED) }
resultOverlappingPanels.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
rec?.map { it.apiName }?.distinct()?.let { apiNames -> rec?.map { it.apiName }?.distinct()?.let { apiNames ->
// very dirty selection // very dirty selection
result_recommendations_filter_button?.isVisible = apiNames.size > 1 recommendationBinding?.resultRecommendationsFilterButton?.apply {
result_recommendations_filter_button?.text = matchAgainst isVisible = apiNames.size > 1
result_recommendations_filter_button?.setOnClickListener { _ -> text = matchAgainst
setOnClickListener { _ ->
activity?.showBottomDialog( activity?.showBottomDialog(
apiNames, apiNames,
apiNames.indexOf(matchAgainst), apiNames.indexOf(matchAgainst),
@ -470,13 +699,9 @@ class ResultFragmentPhone : ResultFragment() {
setRecommendations(rec, apiNames[it]) setRecommendations(rec, apiNames[it])
} }
} }
}
} ?: run { } ?: run {
result_recommendations_filter_button?.isVisible = false recommendationBinding?.resultRecommendationsFilterButton?.isVisible = false
}
result_recommendations?.post {
rec?.let { list ->
(result_recommendations?.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst })
} }
} }
} }

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,29 +92,29 @@ 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(
@ -100,23 +126,26 @@ class ResultFragmentTv : ResultFragment() {
) )
} }
} }
}
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
resultRecommendationsHolder.isGone = isInvalid
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
(result_recommendations?.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst } (resultRecommendationsList.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst }
?: emptyList()) ?: 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,21 +153,34 @@ 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 {
result_episodes?.layoutManager = resultEpisodes.layoutManager =
//LinearListLayout(result_episodes ?: return, result_episodes?.context).apply { LinearListLayout(resultEpisodes.context).apply {
LinearListLayout(result_episodes?.context).apply {
setHorizontal() setHorizontal()
} }
// (result_episodes?.adapter as EpisodeAdapter?)?.apply {
// layout = R.layout.result_episode_both_tv
// }
//result_episodes?.setMaxViewPoolSize(0, Int.MAX_VALUE)
result_season_selection.setAdapter() resultSeasonSelection.setAdapter()
result_range_selection.setAdapter() resultRangeSelection.setAdapter()
result_dub_selection.setAdapter() resultDubSelection.setAdapter()
result_recommendations_filter_selection.setAdapter() 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])
}
}
}
}
observeNullable(viewModel.selectPopup) { popup -> observeNullable(viewModel.selectPopup) { popup ->
if (popup == null) { if (popup == null) {
@ -194,39 +236,42 @@ class ResultFragmentTv : ResultFragment() {
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 {
resultBack.setOnClickListener {
activity?.popCurrentPage() activity?.popCurrentPage()
} }
result_recommendations?.spanCount = 8 resultRecommendationsList.spanCount = 8
result_recommendations?.adapter = resultRecommendationsList.adapter =
SearchAdapter( SearchAdapter(
ArrayList(), ArrayList(),
result_recommendations, resultRecommendationsList,
) { callback -> ) { callback ->
SearchHelper.handleSearchClickCallback(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>
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout <LinearLayout