diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index 8c1ba945..509ea4b9 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -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(activity, R.layout.fragment_player,R.layout.fragment_player_tv) testAllLayouts(activity, R.layout.fragment_player,R.layout.fragment_player_tv) - testAllLayouts(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv) - testAllLayouts(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv) + // testAllLayouts(activity, R.layout.fragment_result,R.layout.fragment_result_tv) + // testAllLayouts(activity, R.layout.fragment_result,R.layout.fragment_result_tv) + + testAllLayouts(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout) + testAllLayouts(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout) + testAllLayouts(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout) testAllLayouts(activity, R.layout.repository_item_tv, R.layout.repository_item) testAllLayouts(activity, R.layout.repository_item_tv, R.layout.repository_item) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 52f7d173..9b72f6c9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -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,42 +250,46 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } val playerSourceMove = if (isShowing) 0f else -50.toPx.toFloat() - player_open_source?.let { - ObjectAnimator.ofFloat(it, "translationY", playerSourceMove).apply { - duration = 200 - start() + + + playerBinding?.apply { + playerOpenSource.let { + ObjectAnimator.ofFloat(it, "translationY", playerSourceMove).apply { + duration = 200 + start() + } } + + if (!isLocked) { + playerFfwdHolder.alpha = 1f + playerRewHolder.alpha = 1f + // player_pause_play_holder?.alpha = 1f + shadowOverlay.isVisible = true + shadowOverlay.startAnimation(fadeAnimation) + playerFfwdHolder.startAnimation(fadeAnimation) + playerRewHolder.startAnimation(fadeAnimation) + playerPausePlay.startAnimation(fadeAnimation) + + /*if (isBuffering) { + player_pause_play?.isVisible = false + player_pause_play_holder?.isVisible = false + } else { + player_pause_play?.isVisible = true + player_pause_play_holder?.startAnimation(fadeAnimation) + player_pause_play?.startAnimation(fadeAnimation) + }*/ + //player_buffering?.startAnimation(fadeAnimation) + } + + bottomPlayerBar.startAnimation(fadeAnimation) + playerOpenSource.startAnimation(fadeAnimation) + playerTopHolder.startAnimation(fadeAnimation) } - - - if (!isLocked) { - player_ffwd_holder?.alpha = 1f - player_rew_holder?.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) - - /*if (isBuffering) { - player_pause_play?.isVisible = false - player_pause_play_holder?.isVisible = false - } else { - player_pause_play?.isVisible = true - player_pause_play_holder?.startAnimation(fadeAnimation) - player_pause_play?.startAnimation(fadeAnimation) - }*/ - //player_buffering?.startAnimation(fadeAnimation) - } - - bottom_player_bar?.startAnimation(fadeAnimation) - player_open_source?.startAnimation(fadeAnimation) - player_top_holder?.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 builder = - AlertDialog.Builder(ctx, R.style.AlertDialogCustom) - .setView(R.layout.subtitle_offset) - val dialog = builder.create() - dialog.show() + val ctx = context ?: return - val beforeOffset = subtitleDelay + val binding = SubtitleOffsetBinding.inflate(LayoutInflater.from(ctx), null, false) - val applyButton = dialog.findViewById(R.id.apply_btt)!! - val cancelButton = dialog.findViewById(R.id.cancel_btt)!! - val input = dialog.findViewById(R.id.subtitle_offset_input)!! - val sub = dialog.findViewById(R.id.subtitle_offset_subtract)!! - val subMore = dialog.findViewById(R.id.subtitle_offset_subtract_more)!! - val add = dialog.findViewById(R.id.subtitle_offset_add)!! - val addMore = dialog.findViewById(R.id.subtitle_offset_add_more)!! - val subTitle = dialog.findViewById(R.id.subtitle_offset_sub_title)!! + val builder = + AlertDialog.Builder(ctx, R.style.AlertDialogCustom) + .setView(binding.root) + val dialog = builder.create() + dialog.show() - input.doOnTextChanged { text, _, _, _ -> - text?.toString()?.toLongOrNull()?.let { - subtitleDelay = it - when { - it > 0L -> { - context?.getString(R.string.subtitle_offset_extra_hint_later_format) - ?.format(it) + val beforeOffset = subtitleDelay + + /*val applyButton = dialog.findViewById(R.id.apply_btt)!! + val cancelButton = dialog.findViewById(R.id.cancel_btt)!! + val input = dialog.findViewById(R.id.subtitle_offset_input)!! + val sub = dialog.findViewById(R.id.subtitle_offset_subtract)!! + val subMore = dialog.findViewById(R.id.subtitle_offset_subtract_more)!! + val add = dialog.findViewById(R.id.subtitle_offset_add)!! + val addMore = dialog.findViewById(R.id.subtitle_offset_add_more)!! + val subTitle = dialog.findViewById(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) } - 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 + 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 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,39 +468,42 @@ 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) + val rotateLeft = AnimationUtils.loadAnimation(context, R.anim.rotate_left) + exoRew.startAnimation(rotateLeft) - val goLeft = AnimationUtils.loadAnimation(context, R.anim.go_left) - goLeft.setAnimationListener(object : Animation.AnimationListener { - override fun onAnimationStart(animation: Animation?) {} + val goLeft = AnimationUtils.loadAnimation(context, R.anim.go_left) + goLeft.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation?) {} - override fun onAnimationRepeat(animation: Animation?) {} + override fun onAnimationRepeat(animation: Animation?) {} - override fun onAnimationEnd(animation: Animation?) { - exo_rew_text?.post { - resetRewindText() - player_center_menu?.isGone = !isShowing - player_rew_holder?.alpha = if (isShowing) 1f else 0f + override fun onAnimationEnd(animation: Animation?) { + exoRewText.post { + resetRewindText() + 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,28 +512,31 @@ 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) + val rotateRight = AnimationUtils.loadAnimation(context, R.anim.rotate_right) + exoFfwd.startAnimation(rotateRight) - val goRight = AnimationUtils.loadAnimation(context, R.anim.go_right) - goRight.setAnimationListener(object : Animation.AnimationListener { - override fun onAnimationStart(animation: Animation?) {} + val goRight = AnimationUtils.loadAnimation(context, R.anim.go_right) + goRight.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation?) {} - override fun onAnimationRepeat(animation: Animation?) {} + override fun onAnimationRepeat(animation: Animation?) {} - override fun onAnimationEnd(animation: Animation?) { - exo_ffwd_text?.post { - resetFastForwardText() - player_center_menu?.isGone = !isShowing - player_ffwd_holder?.alpha = if (isShowing) 1f else 0f + override fun onAnimationEnd(animation: Animation?) { + exoFfwdText.post { + resetFastForwardText() + 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,36 +570,36 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } val fadeTo = if (isLocked) 0f else 1f + playerBinding?.apply { + val fadeAnimation = AlphaAnimation(playerVideoTitle.alpha, fadeTo).apply { + duration = 100 + fillAfter = true + } - val fadeAnimation = AlphaAnimation(player_video_title.alpha, fadeTo).apply { - duration = 100 - fillAfter = true + updateUIVisibility() + // MENUS + //centerMenu.startAnimation(fadeAnimation) + playerPausePlay.startAnimation(fadeAnimation) + playerFfwdHolder.startAnimation(fadeAnimation) + playerRewHolder.startAnimation(fadeAnimation) + + //if (hasEpisodes) + // player_episodes_button?.startAnimation(fadeAnimation) + //player_media_route_button?.startAnimation(fadeAnimation) + //video_bar.startAnimation(fadeAnimation) + + //TITLE + playerVideoTitleRez.startAnimation(fadeAnimation) + playerEpisodeFiller.startAnimation(fadeAnimation) + playerVideoTitle.startAnimation(fadeAnimation) + playerTopHolder.startAnimation(fadeAnimation) + // BOTTOM + playerLockHolder.startAnimation(fadeAnimation) + //player_go_back_holder?.startAnimation(fadeAnimation) + + shadowOverlay.isVisible = true + shadowOverlay.startAnimation(fadeAnimation) } - - updateUIVisibility() - // MENUS - //centerMenu.startAnimation(fadeAnimation) - player_pause_play?.startAnimation(fadeAnimation) - player_ffwd_holder?.startAnimation(fadeAnimation) - player_rew_holder?.startAnimation(fadeAnimation) - - //if (hasEpisodes) - // player_episodes_button?.startAnimation(fadeAnimation) - //player_media_route_button?.startAnimation(fadeAnimation) - //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) - // BOTTOM - player_lock_holder?.startAnimation(fadeAnimation) - //player_go_back_holder?.startAnimation(fadeAnimation) - - shadow_overlay?.isVisible = true - shadow_overlay?.startAnimation(fadeAnimation) - updateLockUI() } @@ -602,34 +613,39 @@ open class FullScreenPlayer : AbstractPlayerFragment() { togglePlayerTitleGone = true } } - player_lock_holder?.isGone = isGone - player_video_bar?.isGone = isGone + playerBinding?.apply { - player_pause_play?.isGone = isGone - //player_buffering?.isGone = isGone - player_top_holder?.isGone = isGone - //player_episodes_button?.isVisible = !isGone && hasEpisodes - player_video_title?.isGone = togglePlayerTitleGone + playerLockHolder.isGone = isGone + playerVideoBar.isGone = isGone + + playerPausePlay.isGone = isGone + //player_buffering?.isGone = isGone + playerTopHolder.isGone = isGone + //player_episodes_button?.isVisible = !isGone && hasEpisodes + playerVideoTitle.isGone = togglePlayerTitleGone // player_video_title_rez?.isGone = isGone - player_episode_filler?.isGone = isGone - player_center_menu?.isGone = isGone - player_lock?.isGone = !isShowing - //player_media_route_button?.isClickable = !isGone - player_go_back_holder?.isGone = isGone - player_sources_btt?.isGone = isGone - player_skip_episode?.isClickable = !isGone + playerEpisodeFiller.isGone = isGone + playerCenterMenu.isGone = isGone + playerLock.isGone = !isShowing + //player_media_route_button?.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) - 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 = - ColorStateList.valueOf(Color.argb(50, color.red, color.green, color.blue)) + 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) { + playerLock.setTextColor(color) + playerLock.iconTint = ColorStateList.valueOf(color) + playerLock.rippleColor = + ColorStateList.valueOf(Color.argb(50, color.red, color.green, color.blue)) + } } } } @@ -638,7 +654,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { 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,249 +797,267 @@ 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 - when (event.action) { - MotionEvent.ACTION_DOWN -> { - // validates if the touch is inside of the player area - isCurrentTouchValid = isValidTouch(currentTouch.x, currentTouch.y) - /*if (isCurrentTouchValid && player_episode_list?.isVisible == true) { - player_episode_list?.isVisible = false - } else*/ if (isCurrentTouchValid) { - currentTouchStartTime = System.currentTimeMillis() - currentTouchStart = currentTouch - currentTouchLast = currentTouch - currentTouchStartPlayerTime = player.getPosition() - getBrightness()?.let { - currentRequestedBrightness = it - } - (activity?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.let { audioManager -> - val currentVolume = - audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) - val maxVolume = - audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + playerBinding?.apply { + playerIntroPlay.isGone = true - currentRequestedVolume = currentVolume.toFloat() / maxVolume.toFloat() - } - } - } - 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 -> - if (abs(seekTo - startTime) > MINIMUM_SEEK_TIME) { - player.seekTo(seekTo) - } - } + when (event.action) { + MotionEvent.ACTION_DOWN -> { + // validates if the touch is inside of the player area + isCurrentTouchValid = isValidTouch(currentTouch.x, currentTouch.y) + /*if (isCurrentTouchValid && player_episode_list?.isVisible == true) { + player_episode_list?.isVisible = false + } else*/ if (isCurrentTouchValid) { + currentTouchStartTime = System.currentTimeMillis() + currentTouchStart = currentTouch + currentTouchLast = currentTouch + currentTouchStartPlayerTime = player.getPosition() + + getBrightness()?.let { + currentRequestedBrightness = it + } + (activity?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.let { audioManager -> + val currentVolume = + audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + val maxVolume = + audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + + currentRequestedVolume = currentVolume.toFloat() / maxVolume.toFloat() } } } - // see if click is eligible for seek 10s - val holdTime = currentTouchStartTime?.minus(System.currentTimeMillis()) - if (isCurrentTouchValid // is valid - && currentTouchAction == null // no other action like swiping is taking place - && currentLastTouchAction == null // last action was none, this prevents mis input random seek - && holdTime != null - && holdTime < DOUBLE_TAB_MAXIMUM_HOLD_TIME // it is a click not a long hold - ) { - if (!isLocked - && (System.currentTimeMillis() - currentLastTouchEndTime) < DOUBLE_TAB_MINIMUM_TIME_BETWEEN // the time since the last action is short - ) { - currentClickCount++ - - if (currentClickCount >= 1) { // have double clicked - currentDoubleTapIndex++ - if (doubleTapPauseEnabled && isFullScreenPlayer) { // you can pause if your tap is in the middle of the screen - when { - currentTouch.x < screenWidth / 2 - (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> { - if (doubleTapEnabled) - rewind() + 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 -> + if (abs(seekTo - startTime) > MINIMUM_SEEK_TIME) { + player.seekTo(seekTo) } - currentTouch.x > screenWidth / 2 + (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> { - if (doubleTapEnabled) - fastForward() - } - else -> { - player.handleEvent(CSPlayerEvent.PlayPauseToggle) - } - } - } else if (doubleTapEnabled && isFullScreenPlayer) { - if (currentTouch.x < screenWidth / 2) { - rewind() - } else { - fastForward() } } } + } + + // see if click is eligible for seek 10s + val holdTime = currentTouchStartTime?.minus(System.currentTimeMillis()) + if (isCurrentTouchValid // is valid + && currentTouchAction == null // no other action like swiping is taking place + && currentLastTouchAction == null // last action was none, this prevents mis input random seek + && holdTime != null + && holdTime < DOUBLE_TAB_MAXIMUM_HOLD_TIME // it is a click not a long hold + ) { + if (!isLocked + && (System.currentTimeMillis() - currentLastTouchEndTime) < DOUBLE_TAB_MINIMUM_TIME_BETWEEN // the time since the last action is short + ) { + currentClickCount++ + + if (currentClickCount >= 1) { // have double clicked + currentDoubleTapIndex++ + if (doubleTapPauseEnabled && isFullScreenPlayer) { // you can pause if your tap is in the middle of the screen + when { + currentTouch.x < screenWidth / 2 - (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> { + if (doubleTapEnabled) + rewind() + } + + currentTouch.x > screenWidth / 2 + (DOUBLE_TAB_PAUSE_PERCENTAGE * screenWidth) -> { + if (doubleTapEnabled) + fastForward() + } + + else -> { + player.handleEvent(CSPlayerEvent.PlayPauseToggle) + } + } + } else if (doubleTapEnabled && isFullScreenPlayer) { + if (currentTouch.x < screenWidth / 2) { + rewind() + } else { + fastForward() + } + } + } + } else { + // is a valid click but not fast enough for seek + currentClickCount = 0 + toggleShowDelayed() + //onClickChange() + } } else { - // is a valid click but not fast enough for seek currentClickCount = 0 - toggleShowDelayed() - //onClickChange() } - } else { - currentClickCount = 0 + + // call auto hide as it wont hide when you have your finger down + autoHide() + + // reset variables + isCurrentTouchValid = false + currentTouchStart = null + currentLastTouchAction = currentTouchAction + currentTouchAction = null + currentTouchStartPlayerTime = null + currentTouchLast = null + currentTouchStartTime = null + + // resets UI + playerTimeText.isVisible = false + playerProgressbarLeftHolder.isVisible = false + playerProgressbarRightHolder.isVisible = false + + currentLastTouchEndTime = System.currentTimeMillis() } - // call auto hide as it wont hide when you have your finger down - autoHide() + MotionEvent.ACTION_MOVE -> { + // if current touch is valid + if (startTouch != null && isCurrentTouchValid && !isLocked && isFullScreenPlayer) { + // action is unassigned and can therefore be assigned + if (currentTouchAction == null) { + val diffFromStart = startTouch - currentTouch - // reset variables - isCurrentTouchValid = false - currentTouchStart = null - currentLastTouchAction = currentTouchAction - currentTouchAction = null - currentTouchStartPlayerTime = null - currentTouchLast = null - currentTouchStartTime = null + if (swipeVerticalEnabled) { + if (abs(diffFromStart.y * 100 / screenHeight) > MINIMUM_VERTICAL_SWIPE) { + // left = Brightness, right = Volume, but the UI is reversed to show the UI better + currentTouchAction = if (startTouch.x < screenWidth / 2) { + // hide the UI if you hold brightness to show screen better, better UX + if (isShowing) { + isShowing = false + animateLayoutChanges() + } - // resets UI - player_time_text?.isVisible = false - player_progressbar_left_holder?.isVisible = false - player_progressbar_right_holder?.isVisible = false - currentLastTouchEndTime = System.currentTimeMillis() - } - MotionEvent.ACTION_MOVE -> { - // if current touch is valid - if (startTouch != null && isCurrentTouchValid && !isLocked && isFullScreenPlayer) { - // action is unassigned and can therefore be assigned - if (currentTouchAction == null) { - val diffFromStart = startTouch - currentTouch - - if (swipeVerticalEnabled) { - if (abs(diffFromStart.y * 100 / screenHeight) > MINIMUM_VERTICAL_SWIPE) { - // left = Brightness, right = Volume, but the UI is reversed to show the UI better - currentTouchAction = if (startTouch.x < screenWidth / 2) { - // hide the UI if you hold brightness to show screen better, better UX - if (isShowing) { - isShowing = false - animateLayoutChanges() - } - - TouchAction.Brightness - } else { - TouchAction.Volume - } - } - } - if (swipeHorizontalEnabled) { - if (abs(diffFromStart.x * 100 / screenHeight) > MINIMUM_HORIZONTAL_SWIPE) { - currentTouchAction = TouchAction.Time - } - } - } - - // display action - val lastTouch = currentTouchLast - if (lastTouch != null) { - val diffFromLast = lastTouch - currentTouch - val verticalAddition = - 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 - - when (currentTouchAction) { - TouchAction.Time -> { - // this simply updates UI as the seek logic happens on release - // startTime is rounded to make the UI sync in a nice way - val startTime = - currentTouchStartPlayerTime?.div(1000L)?.times(1000L) - if (startTime != null) { - calculateNewTime( - startTime, - startTouch, - currentTouch - )?.let { newMs -> - val skipMs = newMs - startTime - player_time_text?.text = - "${convertTimeToString(newMs / 1000)} [${ - (if (abs(skipMs) < 1000) "" else (if (skipMs > 0) "+" else "-")) - }${convertTimeToString(abs(skipMs / 1000))}]" - player_time_text?.isVisible = true + TouchAction.Brightness + } else { + TouchAction.Volume } } } - TouchAction.Brightness -> { - player_progressbar_right_holder?.isVisible = true - val lastRequested = currentRequestedBrightness - currentRequestedBrightness = - min( - 1.0f, - max(currentRequestedBrightness + verticalAddition, 0.0f) - ) - - // this is to not spam request it, just in case it fucks over someone - if (lastRequested != currentRequestedBrightness) - setBrightness(currentRequestedBrightness) - - // max is set high to make it smooth - player_progressbar_right?.max = 100_000 - player_progressbar_right?.progress = - max(2_000, (currentRequestedBrightness * 100_000f).toInt()) - - player_progressbar_right_icon?.setImageResource( - brightnessIcons[min( // clamp the value just in case - brightnessIcons.size - 1, - max( - 0, - round(currentRequestedBrightness * (brightnessIcons.size - 1)).toInt() - ) - )] - ) + if (swipeHorizontalEnabled) { + if (abs(diffFromStart.x * 100 / screenHeight) > MINIMUM_HORIZONTAL_SWIPE) { + currentTouchAction = TouchAction.Time + } } - TouchAction.Volume -> { - (activity?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.let { audioManager -> - player_progressbar_left_holder?.isVisible = true - val maxVolume = - audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) - val currentVolume = - audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + } - // clamps volume and adds swipe - currentRequestedVolume = + // display action + val lastTouch = currentTouchLast + if (lastTouch != null) { + val diffFromLast = lastTouch - currentTouch + val verticalAddition = + diffFromLast.y * VERTICAL_MULTIPLIER / screenHeight.toFloat() + + // update UI + playerTimeText.isVisible = false + playerProgressbarLeftHolder.isVisible = false + playerProgressbarRightHolder.isVisible = false + + when (currentTouchAction) { + TouchAction.Time -> { + // this simply updates UI as the seek logic happens on release + // startTime is rounded to make the UI sync in a nice way + val startTime = + currentTouchStartPlayerTime?.div(1000L)?.times(1000L) + if (startTime != null) { + calculateNewTime( + startTime, + startTouch, + currentTouch + )?.let { newMs -> + val skipMs = newMs - startTime + playerTimeText.apply { + text = + "${convertTimeToString(newMs / 1000)} [${ + (if (abs(skipMs) < 1000) "" else (if (skipMs > 0) "+" else "-")) + }${convertTimeToString(abs(skipMs / 1000))}]" + isVisible = true + } + } + } + } + + TouchAction.Brightness -> { + playerProgressbarRightHolder.isVisible = true + val lastRequested = currentRequestedBrightness + currentRequestedBrightness = min( 1.0f, - max(currentRequestedVolume + verticalAddition, 0.0f) + max(currentRequestedBrightness + verticalAddition, 0.0f) ) - // max is set high to make it smooth - player_progressbar_left?.max = 100_000 - player_progressbar_left?.progress = - max(2_000, (currentRequestedVolume * 100_000f).toInt()) + // this is to not spam request it, just in case it fucks over someone + if (lastRequested != currentRequestedBrightness) + setBrightness(currentRequestedBrightness) - player_progressbar_left_icon?.setImageResource( - volumeIcons[min( // clamp the value just in case - volumeIcons.size - 1, + // max is set high to make it smooth + playerProgressbarRight.max = 100_000 + playerProgressbarRight.progress = + max(2_000, (currentRequestedBrightness * 100_000f).toInt()) + + playerProgressbarRightIcon.setImageResource( + brightnessIcons[min( // clamp the value just in case + brightnessIcons.size - 1, max( 0, - round(currentRequestedVolume * (volumeIcons.size - 1)).toInt() + round(currentRequestedBrightness * (brightnessIcons.size - 1)).toInt() ) )] ) + } - // this is used instead of set volume because old devices does not support it - val desiredVolume = - round(currentRequestedVolume * maxVolume).toInt() - if (desiredVolume != currentVolume) { - val newVolumeAdjusted = - if (desiredVolume < currentVolume) AudioManager.ADJUST_LOWER else AudioManager.ADJUST_RAISE + TouchAction.Volume -> { + (activity?.getSystemService(Context.AUDIO_SERVICE) as? AudioManager)?.let { audioManager -> + playerProgressbarLeftHolder.isVisible = true + val maxVolume = + audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + val currentVolume = + audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) - audioManager.adjustStreamVolume( - AudioManager.STREAM_MUSIC, - newVolumeAdjusted, - 0 + // clamps volume and adds swipe + currentRequestedVolume = + min( + 1.0f, + max(currentRequestedVolume + verticalAddition, 0.0f) + ) + + // max is set high to make it smooth + playerProgressbarLeft.max = 100_000 + playerProgressbarLeft.progress = + max(2_000, (currentRequestedVolume * 100_000f).toInt()) + + playerProgressbarLeftIcon.setImageResource( + volumeIcons[min( // clamp the value just in case + volumeIcons.size - 1, + max( + 0, + round(currentRequestedVolume * (volumeIcons.size - 1)).toInt() + ) + )] ) + + // this is used instead of set volume because old devices does not support it + val desiredVolume = + round(currentRequestedVolume * maxVolume).toInt() + if (desiredVolume != currentVolume) { + val newVolumeAdjusted = + if (desiredVolume < currentVolume) AudioManager.ADJUST_LOWER else AudioManager.ADJUST_RAISE + + audioManager.adjustStreamVolume( + AudioManager.STREAM_MUSIC, + newVolumeAdjusted, + 0 + ) + } } } + + else -> Unit } - else -> Unit } } } @@ -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,93 +1334,97 @@ 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 { - autoHide() - player.handleEvent(CSPlayerEvent.PlayPauseToggle) - } - - skip_chapter_button?.setOnClickListener { - player.handleEvent(CSPlayerEvent.SkipCurrentChapter) - } - - // init clicks - player_resize_btt?.setOnClickListener { - autoHide() - nextResize() - } - - player_speed_btt?.setOnClickListener { - autoHide() - showSpeedDialog() - } - - player_skip_op?.setOnClickListener { - autoHide() - skipOp() - } - - player_skip_episode?.setOnClickListener { - autoHide() - player.handleEvent(CSPlayerEvent.NextEpisode) - } - - player_lock?.setOnClickListener { - autoHide() - toggleLock() - } - - player_subtitle_offset_btt?.setOnClickListener { - showSubtitleOffsetDialog() - } - - exo_rew?.setOnClickListener { - autoHide() - rewind() - } - - exo_ffwd?.setOnClickListener { - autoHide() - fastForward() - } - - player_go_back?.setOnClickListener { - activity?.popCurrentPage() - } - - player_sources_btt?.setOnClickListener { - showMirrorsDialogue() - } - - player_tracks_btt?.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 -> - return@setOnTouchListener handleMotionEvent(callView, event) - } - - exo_progress?.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() - } + playerBinding?.apply { + playerPausePlay.setOnClickListener { + autoHide() + player.handleEvent(CSPlayerEvent.PlayPauseToggle) + } + + skipChapterButton.setOnClickListener { + player.handleEvent(CSPlayerEvent.SkipCurrentChapter) + } + + // init clicks + playerResizeBtt.setOnClickListener { + autoHide() + nextResize() + } + + playerSpeedBtt.setOnClickListener { + autoHide() + showSpeedDialog() + } + + playerSkipOp.setOnClickListener { + autoHide() + skipOp() + } + + playerSkipEpisode.setOnClickListener { + autoHide() + player.handleEvent(CSPlayerEvent.NextEpisode) + } + + playerLock.setOnClickListener { + autoHide() + toggleLock() + } + + playerSubtitleOffsetBtt.setOnClickListener { + showSubtitleOffsetDialog() + } + + exoRew.setOnClickListener { + autoHide() + rewind() + } + + exoFfwd.setOnClickListener { + autoHide() + fastForward() + } + + playerGoBack.setOnClickListener { + activity?.popCurrentPage() + } + + playerSourcesBtt.setOnClickListener { + showMirrorsDialogue() + } + + 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 + playerHolder.setOnTouchListener { callView, event -> + return@setOnTouchListener handleMotionEvent(callView, 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 } - return@setOnTouchListener false } // init UI try { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 08efa5e7..8a7eefbc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -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() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 0cf74e12..0b7c00ed 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -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(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! } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index e3c8ef06..4c711126 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -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 = emptyList() var currentTrailerIndex = 0 @@ -96,19 +125,30 @@ 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 { - val fadeIn: Animation = AlphaAnimation(alpha, if (turnVis) 1.0f else 0.0f).apply { - interpolator = DecelerateInterpolator() - duration = 200 - fillAfter = true + resultBinding?.apply { + resultSmallscreenHolder.isVisible = turnVis + resultPosterBackgroundHolder.apply { + val fadeIn: Animation = AlphaAnimation(alpha, if (turnVis) 1.0f else 0.0f).apply { + interpolator = DecelerateInterpolator() + duration = 200 + fillAfter = true + } + clearAnimation() + startAnimation(fadeIn) } - clearAnimation() - startAnimation(fadeIn) + + // We don't want the trailer to be focusable if it's not visible + resultSmallscreenHolder.descendantFocusability = if (isSuccess) { + ViewGroup.FOCUS_AFTER_DESCENDANTS + } else { + ViewGroup.FOCUS_BLOCK_DESCENDANTS + } + binding?.resultFullscreenHolder?.isVisible = !isSuccess && isFullScreenPlayer } + //player_view?.apply { //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?) { @@ -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) + + recommendationBinding?.resultRecommendationsList?.apply { + spanCount = 3 + adapter = + SearchAdapter( + ArrayList(), + this, + ) { callback -> + SearchHelper.handleSearchClickCallback(callback) + } + } - result_recommendations?.spanCount = 3 - result_recommendations?.adapter = - SearchAdapter( - ArrayList(), - result_recommendations, - ) { callback -> - SearchHelper.handleSearchClickCallback(callback) - } PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this) - 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(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) - // 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) + selectSeason = + text?.asStringNull(resultSeasonButton.context) + // If the season button is visible the result season button will be next focus down + 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) - - // 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) + resultBinding?.apply { + resultEpisodeSelect.setText(range) + // If Season button is invisible then the bookmark button next focus is 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,50 +650,58 @@ class ResultFragmentPhone : ResultFragment() { } override fun onGestureRegionsUpdate(gestureRegions: List) { - result_overlapping_panels?.setChildGestureRegions(gestureRegions) + binding?.resultOverlappingPanels?.setChildGestureRegions(gestureRegions) } override fun setRecommendations(rec: List?, 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() - R.id.result_recommendations - } else { - result_overlapping_panels?.closePanels() - R.id.result_description - } - - result_recommendations_btt?.nextFocusDownId = nextFocusDown - result_search?.nextFocusDownId = nextFocusDown - result_open_in_browser?.nextFocusDownId = nextFocusDown - result_share?.nextFocusDownId = nextFocusDown - } - result_overlapping_panels?.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED) - val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName - 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 { _ -> - activity?.showBottomDialog( - apiNames, - apiNames.indexOf(matchAgainst), - getString(R.string.home_change_provider_img_des), false, {} - ) { - setRecommendations(rec, apiNames[it]) + + recommendationBinding?.apply { + root.isGone = isInvalid + root.post { + rec?.let { list -> + (resultRecommendationsList.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst }) } } - } ?: run { - result_recommendations_filter_button?.isVisible = false } - result_recommendations?.post { - rec?.let { list -> - (result_recommendations?.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 { + resultOverlappingPanels.closePanels() + R.id.result_description + } + resultBinding?.apply { + resultRecommendationsBtt.nextFocusDownId = nextFocusDown + resultSearch.nextFocusDownId = nextFocusDown + resultOpenInBrowser.nextFocusDownId = nextFocusDown + resultShare.nextFocusDownId = nextFocusDown + } + } + resultOverlappingPanels.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED) + + rec?.map { it.apiName }?.distinct()?.let { apiNames -> + // very dirty selection + recommendationBinding?.resultRecommendationsFilterButton?.apply { + isVisible = apiNames.size > 1 + text = matchAgainst + setOnClickListener { _ -> + activity?.showBottomDialog( + apiNames, + apiNames.indexOf(matchAgainst), + getString(R.string.home_change_provider_img_des), false, {} + ) { + setRecommendations(rec, apiNames[it]) + } + } + } + } ?: run { + recommendationBinding?.resultRecommendationsFilterButton?.isVisible = false } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index bb2a6e09..73934490 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -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 = 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,57 +92,60 @@ 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>?) { super.updateEpisodes(episodes) if (episodes is Resource.Success && hasNoFocus()) { - result_episodes?.requestFocus() + binding?.resultEpisodes?.requestFocus() } } override fun updateMovie(data: Resource>?) { super.updateMovie(data) if (data is Resource.Success && hasNoFocus()) { - result_play_movie?.requestFocus() + binding?.resultPlayMovie?.requestFocus() } } override fun setTrailers(trailers: List?) { context?.updateHasTrailers() if (!LoadResponse.isTrailersEnabled) return - - result_play_trailer?.isGone = trailers.isNullOrEmpty() - result_play_trailer?.setOnClickListener { - if (trailers.isNullOrEmpty()) return@setOnClickListener - activity.navigate( - R.id.global_to_navigation_player, GeneratorPlayer.newInstance( - ExtractorLinkGenerator( - trailers, - emptyList() + binding?.resultPlayTrailer?.apply { + isGone = trailers.isNullOrEmpty() + setOnClickListener { + if (trailers.isNullOrEmpty()) return@setOnClickListener + activity.navigate( + R.id.global_to_navigation_player, GeneratorPlayer.newInstance( + ExtractorLinkGenerator( + trailers, + emptyList() + ) ) ) - ) + } } } override fun setRecommendations(rec: List?, validApiName: String?) { currentRecommendations = rec ?: emptyList() val isInvalid = rec.isNullOrEmpty() - result_recommendations?.isGone = isInvalid - result_recommendations_holder?.isGone = isInvalid - val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName - (result_recommendations?.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst } - ?: emptyList()) + binding?.apply { + resultRecommendationsList.isGone = isInvalid + resultRecommendationsHolder.isGone = isInvalid + val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName + (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)) - } ?: run { - result_recommendations_filter_selection?.isVisible = false + rec?.map { it.apiName }?.distinct()?.let { apiNames -> + // very dirty selection + resultRecommendationsFilterSelection.isVisible = apiNames.size > 1 + resultRecommendationsFilterSelection.update(apiNames.map { txt(it) to it }) + resultRecommendationsFilterSelection.select(apiNames.indexOf(matchAgainst)) + } ?: run { + 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) + binding?.apply { + resultEpisodes.layoutManager = + LinearListLayout(resultEpisodes.context).apply { + setHorizontal() + } - result_episodes?.layoutManager = - //LinearListLayout(result_episodes ?: return, result_episodes?.context).apply { - LinearListLayout(result_episodes?.context).apply { - setHorizontal() + 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]) + } + } } - // (result_episodes?.adapter as EpisodeAdapter?)?.apply { - // layout = R.layout.result_episode_both_tv - // } - //result_episodes?.setMaxViewPoolSize(0, Int.MAX_VALUE) + } - result_season_selection.setAdapter() - result_range_selection.setAdapter() - result_dub_selection.setAdapter() - result_recommendations_filter_selection.setAdapter() observeNullable(viewModel.selectPopup) { popup -> - if(popup == null) { + if (popup == null) { popupDialog?.dismissSafe(activity) popupDialog = null return@observeNullable @@ -166,67 +208,70 @@ class ResultFragmentTv : ResultFragment() { } observeNullable(viewModel.loadedLinks) { load -> - if(load == null) { + if (load == null) { loadingDialog?.dismissSafe(activity) loadingDialog = null return@observeNullable } - if (loadingDialog?.isShowing != true) { - loadingDialog?.dismissSafe(activity) - loadingDialog = null - } - loadingDialog = loadingDialog ?: context?.let { ctx -> - val builder = BottomSheetDialog(ctx) - builder.setContentView(R.layout.bottom_loading) - builder.setOnDismissListener { - loadingDialog = null - viewModel.cancelLinks() - } - //builder.setOnCancelListener { - // it?.dismiss() - //} - builder.setCanceledOnTouchOutside(true) - builder.show() - builder - } + if (loadingDialog?.isShowing != true) { + loadingDialog?.dismissSafe(activity) + loadingDialog = null + } + loadingDialog = loadingDialog ?: context?.let { ctx -> + val builder = BottomSheetDialog(ctx) + builder.setContentView(R.layout.bottom_loading) + builder.setOnDismissListener { + loadingDialog = null + viewModel.cancelLinks() + } + //builder.setOnCancelListener { + // it?.dismiss() + //} + builder.setCanceledOnTouchOutside(true) + builder.show() + builder + } } 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 { - activity?.popCurrentPage() - } - - result_recommendations?.spanCount = 8 - result_recommendations?.adapter = - SearchAdapter( - ArrayList(), - result_recommendations, - ) { callback -> - SearchHelper.handleSearchClickCallback(callback) + binding?.apply { + resultBack.setOnClickListener { + activity?.popCurrentPage() } + + resultRecommendationsList.spanCount = 8 + resultRecommendationsList.adapter = + SearchAdapter( + ArrayList(), + resultRecommendationsList, + ) { callback -> + SearchHelper.handleSearchClickCallback(callback) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt index bf47209a..d1667229 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultTrailerPlayer.kt @@ -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() diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index d4bdc370..ee3477b0 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -277,7 +277,9 @@ android:layout_height="wrap_content" android:descendantFocusability="blocksDescendants"> - + diff --git a/app/src/main/res/layout/fragment_result_swipe.xml b/app/src/main/res/layout/fragment_result_swipe.xml index 27729bf8..4e8e3c14 100644 --- a/app/src/main/res/layout/fragment_result_swipe.xml +++ b/app/src/main/res/layout/fragment_result_swipe.xml @@ -189,7 +189,7 @@ android:layout_height="match_parent" android:layout_gravity="start"> - + - + @@ -209,7 +209,7 @@ android:layout_height="match_parent" android:layout_gravity="end"> - + diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index a9299195..f74dc339 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -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 @@ + + + + + + + + + + + + + + app:layout_constraintBottom_toTopOf="@id/result_recommendations_list" /> diff --git a/app/src/main/res/layout/trailer_custom_layout.xml b/app/src/main/res/layout/trailer_custom_layout.xml index 465e2674..8ae2b1b5 100644 --- a/app/src/main/res/layout/trailer_custom_layout.xml +++ b/app/src/main/res/layout/trailer_custom_layout.xml @@ -75,11 +75,118 @@ android:src="@drawable/play_button" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +