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

View file

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

View file

@ -4,26 +4,19 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.Intent.*
import android.content.res.ColorStateList
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.discord.panels.OverlappingPanelsLayout
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
@ -38,7 +31,6 @@ import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
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.DataStoreHelper.getVideoWatchState
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.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
@ -364,8 +355,9 @@ open class ResultFragment : ResultTrailerPlayer() {
return@setOnLongClickListener true
}
val show = viewModel.currentRepo?.api?.hasDownloadSupport == true && !isTvSettings()
if(show) {
val show =
viewModel.currentRepo?.api?.hasDownloadSupport == true && !isTvSettings()
if (show) {
download_button?.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached(
ep.name,
@ -379,7 +371,6 @@ open class ResultFragment : ResultTrailerPlayer() {
System.currentTimeMillis(),
)
) { click ->
println("Click:$click")
when (click.action) {
DOWNLOAD_ACTION_DOWNLOAD -> {
viewModel.handleAction(
@ -393,7 +384,6 @@ open class ResultFragment : ResultTrailerPlayer() {
}
}
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
val isTv = isTvSettings()
@ -636,152 +590,6 @@ open class ResultFragment : ResultTrailerPlayer() {
result_episode_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 ->
if (resume == null) {
@ -841,10 +649,6 @@ open class ResultFragment : ResultTrailerPlayer() {
if (hasFocus) result_bookmark_button?.requestFocus()
}
result_sync_set_score?.setOnClickListener {
syncModel.publishUserData()
}
observe(viewModel.trailers) { trailers ->
setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet!
}

View file

@ -1,28 +1,47 @@
package com.lagradost.cloudstream3.ui.result
import android.app.Dialog
import android.content.res.ColorStateList
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.animation.DecelerateInterpolator
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.core.widget.doOnTextChanged
import com.discord.panels.OverlappingPanelsLayout
import com.discord.panels.PanelsChildGestureRegionObserver
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
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.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.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.search.SearchAdapter
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.showBottomDialogInstant
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.popCurrentPage
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() {
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 currentTrailerIndex = 0
@ -96,10 +125,11 @@ class ResultFragmentPhone : ResultFragment() {
//result_trailer_thumbnail?.setImageBitmap(result_poster_background?.drawable?.toBitmap())
result_trailer_loading?.isVisible = isSuccess
// result_trailer_loading?.isVisible = isSuccess
val turnVis = !isSuccess && !isFullScreenPlayer
result_smallscreen_holder?.isVisible = turnVis
result_poster_background_holder?.apply {
resultBinding?.apply {
resultSmallscreenHolder.isVisible = turnVis
resultPosterBackgroundHolder.apply {
val fadeIn: Animation = AlphaAnimation(alpha, if (turnVis) 1.0f else 0.0f).apply {
interpolator = DecelerateInterpolator()
duration = 200
@ -109,6 +139,16 @@ class ResultFragmentPhone : ResultFragment() {
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 {
//alpha = 0.0f
//ObjectAnimator.ofFloat(player_view, "alpha", 1f).apply {
@ -124,13 +164,7 @@ class ResultFragmentPhone : ResultFragment() {
//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>?) {
@ -144,12 +178,15 @@ class ResultFragmentPhone : ResultFragment() {
//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
PanelsChildGestureRegionObserver.Provider.get().let { obs ->
result_cast_items?.let {
resultBinding?.resultCastItems?.let {
obs.unregister(it)
}
obs.removeGestureRegionsUpdateListener(this)
}
binding = null
resultBinding = null
syncBinding = null
recommendationBinding = null
super.onDestroyView()
}
@ -173,30 +210,35 @@ class ResultFragmentPhone : ResultFragment() {
super.onViewCreated(view, savedInstanceState)
player_open_source?.setOnClickListener {
playerBinding?.playerOpenSource?.setOnClickListener {
currentTrailers.getOrNull(currentTrailerIndex)?.let {
context?.openBrowser(it.url)
}
}
result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
result_overlapping_panels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
binding?.resultOverlappingPanels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
binding?.resultOverlappingPanels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
result_recommendations?.spanCount = 3
result_recommendations?.adapter =
recommendationBinding?.resultRecommendationsList?.apply {
spanCount = 3
adapter =
SearchAdapter(
ArrayList(),
result_recommendations,
this,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
}
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
result_cast_items?.let {
resultBinding?.resultCastItems?.let {
PanelsChildGestureRegionObserver.Provider.get().register(it)
}
result_back?.setOnClickListener {
binding?.resultBack?.setOnClickListener {
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,
clickCallback = { action ->
if (action == IMAGE_CLICK || action == IMAGE_LONG_CLICK) {
if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) {
result_overlapping_panels?.openStartPanel()
if (binding?.resultOverlappingPanels?.getSelectedPanel()?.ordinal == 1) {
binding?.resultOverlappingPanels?.openStartPanel()
} 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
if (dy > 0) { //check for scroll down
result_bookmark_fab?.shrink()
binding?.resultBookmarkFab?.shrink()
} else if (dy < -5) {
result_bookmark_fab?.extend()
binding?.resultBookmarkFab?.extend()
}
if (!isFullScreenPlayer && player.getIsPlaying()) {
if (scrollY > (player_background?.height ?: scrollY)) {
if (scrollY > (resultBinding?.fragmentTrailer?.playerBackground?.height
?: scrollY)
) {
player.handleEvent(CSPlayerEvent.Pause)
}
}
@ -240,11 +284,11 @@ class ResultFragmentPhone : ResultFragment() {
})
val api = APIHolder.getApiFromNameNull(apiName)
if (media_route_button != null) {
binding?.mediaRouteButton?.apply {
val chromecastSupport = api?.hasChromecastSupport == true
media_route_button?.alpha = if (chromecastSupport) 1f else 0.3f
alpha = if (chromecastSupport) 1f else 0.3f
if (!chromecastSupport) {
media_route_button?.setOnClickListener {
setOnClickListener {
CommonActivity.showToast(
R.string.no_chromecast_support_toast,
Toast.LENGTH_LONG
@ -254,10 +298,9 @@ class ResultFragmentPhone : ResultFragment() {
activity?.let { act ->
if (act.isCastApiAvailable()) {
try {
CastButtonFactory.setUpMediaRouteButton(act, media_route_button)
CastButtonFactory.setUpMediaRouteButton(act, this)
val castContext = CastContext.getSharedInstance(act.applicationContext)
media_route_button?.isGone =
castContext.castState == CastState.NO_DEVICES_AVAILABLE
isGone = castContext.castState == CastState.NO_DEVICES_AVAILABLE
// this shit leaks for some reason
//castContext.addCastStateListener { state ->
// media_route_button?.isGone = state == CastState.NO_DEVICES_AVAILABLE
@ -270,7 +313,7 @@ class ResultFragmentPhone : ResultFragment() {
}
observeNullable(viewModel.episodesCountText) { count ->
result_episodes_text.setText(count)
resultBinding?.resultEpisodesText.setText(count)
}
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 ->
if (load == null) {
loadingDialog?.dismissSafe(activity)
@ -327,46 +549,41 @@ class ResultFragmentPhone : ResultFragment() {
}
observeNullable(viewModel.selectedSeason) { text ->
result_season_button.setText(text)
resultBinding?.apply {
resultSeasonButton.setText(text)
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 (result_season_button?.isVisible == true)
if (result_resume_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_season_button)
//else
// setFocusUpAndDown(result_bookmark_button, result_season_button)
if (resultSeasonButton.isVisible && resultResumeParent.isVisible) {
setFocusUpAndDown(resultResumeSeriesButton, resultSeasonButton)
}
}
}
observeNullable(viewModel.selectedDubStatus) { status ->
result_dub_select?.setText(status)
resultBinding?.apply {
resultDubSelect.setText(status)
if (result_dub_select?.isVisible == true)
if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) {
if (result_resume_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_dub_select)
//else
// setFocusUpAndDown(result_bookmark_button, result_dub_select)
if (resultDubSelect.isVisible && !resultSeasonButton.isVisible && !resultEpisodeSelect.isVisible && resultResumeParent.isVisible) {
setFocusUpAndDown(resultResumeSeriesButton, resultDubSelect)
}
}
}
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 (result_episode_select?.isVisible == true)
if (result_season_button?.isVisible != true) {
if (result_resume_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_episode_select)
//else
// setFocusUpAndDown(result_bookmark_button, result_episode_select)
if (resultEpisodeSelect.isVisible && !resultSeasonButton.isVisible && resultResumeParent.isVisible) {
setFocusUpAndDown(resultResumeSeriesButton, resultEpisodeSelect)
}
}
}
// val preferDub = context?.getApiDubstatusSettings()?.all { it == DubStatus.Dubbed } == true
observe(viewModel.dubSubSelections) { range ->
result_dub_select.setOnClickListener { view ->
resultBinding?.resultDubSelect?.setOnClickListener { view ->
view?.context?.let { ctx ->
view.popupMenuNoIconsAndNoStringRes(range
.mapNotNull { (text, status) ->
@ -382,7 +599,7 @@ class ResultFragmentPhone : ResultFragment() {
}
observe(viewModel.rangeSelections) { range ->
result_episode_select?.setOnClickListener { view ->
resultBinding?.resultEpisodeSelect?.setOnClickListener { view ->
view?.context?.let { ctx ->
val names = range
.mapNotNull { (text, r) ->
@ -399,7 +616,7 @@ class ResultFragmentPhone : ResultFragment() {
}
observe(viewModel.seasonSelections) { seasonList ->
result_season_button?.setOnClickListener { view ->
resultBinding?.resultSeasonButton?.setOnClickListener { view ->
view?.context?.let { ctx ->
val names = seasonList
@ -433,35 +650,47 @@ class ResultFragmentPhone : ResultFragment() {
}
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {
result_overlapping_panels?.setChildGestureRegions(gestureRegions)
binding?.resultOverlappingPanels?.setChildGestureRegions(gestureRegions)
}
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
val isInvalid = rec.isNullOrEmpty()
result_recommendations?.isGone = isInvalid
result_recommendations_btt?.isGone = isInvalid
result_recommendations_btt?.setOnClickListener {
val nextFocusDown = if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) {
result_overlapping_panels?.openEndPanel()
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
recommendationBinding?.apply {
root.isGone = isInvalid
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
} else {
result_overlapping_panels?.closePanels()
resultOverlappingPanels.closePanels()
R.id.result_description
}
result_recommendations_btt?.nextFocusDownId = nextFocusDown
result_search?.nextFocusDownId = nextFocusDown
result_open_in_browser?.nextFocusDownId = nextFocusDown
result_share?.nextFocusDownId = nextFocusDown
resultBinding?.apply {
resultRecommendationsBtt.nextFocusDownId = nextFocusDown
resultSearch.nextFocusDownId = nextFocusDown
resultOpenInBrowser.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 ->
// very dirty selection
result_recommendations_filter_button?.isVisible = apiNames.size > 1
result_recommendations_filter_button?.text = matchAgainst
result_recommendations_filter_button?.setOnClickListener { _ ->
recommendationBinding?.resultRecommendationsFilterButton?.apply {
isVisible = apiNames.size > 1
text = matchAgainst
setOnClickListener { _ ->
activity?.showBottomDialog(
apiNames,
apiNames.indexOf(matchAgainst),
@ -470,13 +699,9 @@ class ResultFragmentPhone : ResultFragment() {
setRecommendations(rec, apiNames[it])
}
}
} ?: run {
result_recommendations_filter_button?.isVisible = false
}
result_recommendations?.post {
rec?.let { list ->
(result_recommendations?.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst })
} ?: run {
recommendationBinding?.resultRecommendationsFilterButton?.isVisible = false
}
}
}

View file

@ -1,8 +1,11 @@
package com.lagradost.cloudstream3.ui.result
import android.app.Dialog
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
@ -12,23 +15,43 @@ 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.FragmentResultTvBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import kotlinx.android.synthetic.main.fragment_result_tv.*
class ResultFragmentTv : ResultFragment() {
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 fun handleSelection(data: Any) {
@ -36,12 +59,15 @@ class ResultFragmentTv : ResultFragment() {
is EpisodeRange -> {
viewModel.changeRange(data)
}
is Int -> {
viewModel.changeSeason(data)
}
is DubStatus -> {
viewModel.changeDubStatus(data)
}
is String -> {
setRecommendations(currentRecommendations, data)
}
@ -66,29 +92,29 @@ class ResultFragmentTv : ResultFragment() {
private fun hasNoFocus(): Boolean {
val focus = activity?.currentFocus
if (focus == null || !focus.isVisible) return true
return focus == this.result_root
return focus == binding?.resultRoot
}
override fun updateEpisodes(episodes: Resource<List<ResultEpisode>>?) {
super.updateEpisodes(episodes)
if (episodes is Resource.Success && hasNoFocus()) {
result_episodes?.requestFocus()
binding?.resultEpisodes?.requestFocus()
}
}
override fun updateMovie(data: Resource<Pair<UiText, ResultEpisode>>?) {
super.updateMovie(data)
if (data is Resource.Success && hasNoFocus()) {
result_play_movie?.requestFocus()
binding?.resultPlayMovie?.requestFocus()
}
}
override fun setTrailers(trailers: List<ExtractorLink>?) {
context?.updateHasTrailers()
if (!LoadResponse.isTrailersEnabled) return
result_play_trailer?.isGone = trailers.isNullOrEmpty()
result_play_trailer?.setOnClickListener {
binding?.resultPlayTrailer?.apply {
isGone = trailers.isNullOrEmpty()
setOnClickListener {
if (trailers.isNullOrEmpty()) return@setOnClickListener
activity.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
@ -100,23 +126,26 @@ class ResultFragmentTv : ResultFragment() {
)
}
}
}
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
currentRecommendations = rec ?: emptyList()
val isInvalid = rec.isNullOrEmpty()
result_recommendations?.isGone = isInvalid
result_recommendations_holder?.isGone = isInvalid
binding?.apply {
resultRecommendationsList.isGone = isInvalid
resultRecommendationsHolder.isGone = isInvalid
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())
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
// very dirty selection
result_recommendations_filter_selection?.isVisible = apiNames.size > 1
result_recommendations_filter_selection?.update(apiNames.map { txt(it) to it })
result_recommendations_filter_selection?.select(apiNames.indexOf(matchAgainst))
resultRecommendationsFilterSelection.isVisible = apiNames.size > 1
resultRecommendationsFilterSelection.update(apiNames.map { txt(it) to it })
resultRecommendationsFilterSelection.select(apiNames.indexOf(matchAgainst))
} ?: run {
result_recommendations_filter_selection?.isVisible = false
resultRecommendationsFilterSelection.isVisible = false
}
}
}
@ -124,24 +153,37 @@ class ResultFragmentTv : ResultFragment() {
var popupDialog: Dialog? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
result_episodes?.layoutManager =
//LinearListLayout(result_episodes ?: return, result_episodes?.context).apply {
LinearListLayout(result_episodes?.context).apply {
binding?.apply {
resultEpisodes.layoutManager =
LinearListLayout(resultEpisodes.context).apply {
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()
result_range_selection.setAdapter()
result_dub_selection.setAdapter()
result_recommendations_filter_selection.setAdapter()
resultSeasonSelection.setAdapter()
resultRangeSelection.setAdapter()
resultDubSelection.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 ->
if(popup == null) {
if (popup == null) {
popupDialog?.dismissSafe(activity)
popupDialog = null
return@observeNullable
@ -166,7 +208,7 @@ class ResultFragmentTv : ResultFragment() {
}
observeNullable(viewModel.loadedLinks) { load ->
if(load == null) {
if (load == null) {
loadingDialog?.dismissSafe(activity)
loadingDialog = null
return@observeNullable
@ -194,39 +236,42 @@ class ResultFragmentTv : ResultFragment() {
observeNullable(viewModel.episodesCountText) { count ->
result_episodes_text.setText(count)
binding?.resultEpisodesText.setText(count)
}
observe(viewModel.selectedRangeIndex) { selected ->
result_range_selection.select(selected)
binding?.resultRangeSelection.select(selected)
}
observe(viewModel.selectedSeasonIndex) { selected ->
result_season_selection.select(selected)
binding?.resultSeasonSelection.select(selected)
}
observe(viewModel.selectedDubStatusIndex) { selected ->
result_dub_selection.select(selected)
binding?.resultDubSelection.select(selected)
}
observe(viewModel.rangeSelections) {
result_range_selection.update(it)
binding?.resultRangeSelection.update(it)
}
observe(viewModel.dubSubSelections) {
result_dub_selection.update(it)
binding?.resultDubSelection.update(it)
}
observe(viewModel.seasonSelections) {
result_season_selection.update(it)
binding?.resultSeasonSelection.update(it)
}
result_back?.setOnClickListener {
binding?.apply {
resultBack.setOnClickListener {
activity?.popCurrentPage()
}
result_recommendations?.spanCount = 8
result_recommendations?.adapter =
resultRecommendationsList.spanCount = 8
resultRecommendationsList.adapter =
SearchAdapter(
ArrayList(),
result_recommendations,
resultRecommendationsList,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
}
}
}

View file

@ -13,6 +13,7 @@ import androidx.core.view.isVisible
import com.discord.panels.PanelsChildGestureRegionObserver
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.player.FullScreenPlayer
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.IOnBackPressed
import kotlinx.android.synthetic.main.fragment_result.*
@ -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_tv.*
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 {
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 =
FrameLayout.LayoutParams(
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 {
val anim = ValueAnimator.ofInt(
@ -131,7 +131,8 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
private fun updateFullscreen(fullscreen: Boolean) {
isFullScreenPlayer = 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) {
enterFullscreen()
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?) {
super.onViewCreated(view, savedInstanceState)
player_fullscreen?.setOnClickListener {
playerBinding?.playerFullscreen?.setOnClickListener {
updateFullscreen(!isFullScreenPlayer)
}
updateFullscreen(isFullScreenPlayer)
uiReset()
player_intro_play?.setOnClickListener {
player_intro_play?.isGone = true
playerBinding?.playerIntroPlay?.setOnClickListener {
playerBinding?.playerIntroPlay?.isGone = true
player.handleEvent(CSPlayerEvent.Play)
updateUIVisibility()
fixPlayerSize()

View file

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

View file

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

View file

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

View file

@ -84,6 +84,33 @@
style="@style/SmallBlackButton"
android:text="@string/filler" />
</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
android:id="@+id/player_video_holder"

View file

@ -167,6 +167,34 @@
app:layout_constraintRight_toRightOf="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
android:id="@+id/player_video_holder"
android:layout_width="match_parent"

View file

@ -20,7 +20,7 @@
android:layout_marginBottom="10dp"
android:layout_marginStart="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
android:descendantFocusability="afterDescendants"
@ -30,7 +30,7 @@
android:layout_height="wrap_content"
android:clipToPadding="false"
app:spanCount="3"
android:id="@+id/result_recommendations"
android:id="@+id/result_recommendations_list"
tools:listitem="@layout/search_result_grid"
android:orientation="vertical" />
</LinearLayout>

View file

@ -75,11 +75,118 @@
android:src="@drawable/play_button" />
</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
android:id="@+id/player_video_holder"
android:layout_width="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-->
<ProgressBar
android:id="@+id/player_buffering"
@ -282,6 +389,11 @@
tools:ignore="ContentDescription" />
</LinearLayout>
<FrameLayout
android:id="@+id/piphide"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/player_open_source"
android:layout_width="24dp"
@ -366,7 +478,110 @@
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24"
app:tint="@color/white" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="60dp"
android:gravity="center"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/player_lock"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_skip_episode"
android:nextFocusRight="@id/player_resize_btt"
android:text="@string/video_lock"
app:icon="@drawable/video_locked"
app:iconSize="30dp" />
<LinearLayout
android:id="@+id/player_lock_holder"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/player_resize_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_lock"
android:nextFocusRight="@id/player_speed_btt"
android:text="@string/video_aspect_ratio_resize"
app:icon="@drawable/ic_baseline_aspect_ratio_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_speed_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_resize_btt"
android:nextFocusRight="@id/player_subtitle_offset_btt"
app:icon="@drawable/ic_baseline_speed_24"
tools:text="Speed" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_subtitle_offset_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_speed_btt"
android:nextFocusRight="@id/player_sources_btt"
android:text="@string/subtitle_offset"
android:visibility="gone"
app:icon="@drawable/ic_outline_subtitles_24"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_sources_btt"
style="@style/VideoButton"
android:layout_height="40dp"
android:nextFocusLeft="@id/player_subtitle_offset_btt"
android:nextFocusRight="@id/player_tracks_btt"
android:text="@string/video_source"
app:icon="@drawable/ic_baseline_playlist_play_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_tracks_btt"
style="@style/VideoButton"
android:layout_height="40dp"
android:nextFocusLeft="@id/player_sources_btt"
android:nextFocusRight="@id/player_skip_op"
android:text="@string/tracks"
app:icon="@drawable/ic_baseline_playlist_play_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_skip_op"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_sources_btt"
android:nextFocusRight="@id/player_skip_episode"
android:text="@string/video_skip_op"
app:icon="@drawable/ic_baseline_fast_forward_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_skip_episode"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_skip_op"
android:nextFocusRight="@id/player_lock"
android:text="@string/next_episode"
app:icon="@drawable/ic_baseline_skip_next_24" />
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout