focus fix + rtl fix

This commit is contained in:
LagradOst 2023-07-27 21:47:42 +02:00
parent a1824c86a3
commit c98f35fd94
13 changed files with 284 additions and 126 deletions

View File

@ -13,16 +13,15 @@ import android.os.Bundle
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log import android.util.Log
import android.view.* import android.view.*
import android.view.animation.Animation
import android.view.animation.AnimationSet
import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.annotation.MainThread
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.doOnLayout import androidx.core.animation.addListener
import androidx.core.view.ViewCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@ -35,22 +34,12 @@ import androidx.navigation.NavOptions
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.ChangeBounds
import androidx.transition.ChangeTransform
import androidx.transition.Scene
import androidx.transition.TransitionManager
import androidx.transition.TransitionManager.beginDelayedTransition
import androidx.transition.TransitionSet
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.google.android.gms.cast.framework.* import com.google.android.gms.cast.framework.*
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.navigationrail.NavigationRailView import com.google.android.material.navigationrail.NavigationRailView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
@ -108,6 +97,7 @@ import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.isLtr
import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable
import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
@ -131,6 +121,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.requestRW import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.Requests
import com.lagradost.nicehttp.ResponseParser import com.lagradost.nicehttp.ResponseParser
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -140,6 +131,7 @@ import java.lang.ref.WeakReference
import java.net.URI import java.net.URI
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.charset.Charset import java.nio.charset.Charset
import kotlin.math.absoluteValue
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -472,13 +464,24 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
binding?.navHostFragment?.apply { binding?.navHostFragment?.apply {
val params = layoutParams as ConstraintLayout.LayoutParams val params = layoutParams as ConstraintLayout.LayoutParams
val push = if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0
if(!this.isLtr()) {
params.setMargins(
params.leftMargin,
params.topMargin,
push,
params.bottomMargin
)
} else {
params.setMargins(
push,
params.topMargin,
params.rightMargin,
params.bottomMargin
)
}
params.setMargins(
if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0,
params.topMargin,
params.rightMargin,
params.bottomMargin
)
layoutParams = params layoutParams = params
} }
@ -572,9 +575,22 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
} }
override fun dispatchKeyEvent(event: KeyEvent?): Boolean { override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
CommonActivity.dispatchKeyEvent(this, event)?.let { val start = System.currentTimeMillis()
return it try {
val response = CommonActivity.dispatchKeyEvent(this, event)
if (response != null)
return response
} finally {
debugAssert({
val end = System.currentTimeMillis()
val delta = end - start
delta > 100
}) {
"Took over 100ms to navigate, smth is VERY wrong"
}
} }
return super.dispatchKeyEvent(event) return super.dispatchKeyEvent(event)
} }
@ -745,85 +761,186 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
} }
private var binding: ActivityMainBinding? = null private var binding: ActivityMainBinding? = null
private var focusOutline: WeakReference<View> = WeakReference(null)
private var lastFocus: WeakReference<View> = WeakReference(null)
private val layoutListener: View.OnLayoutChangeListener =
View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
updateFocusView(
v
)
}
private val attachListener : View.OnAttachStateChangeListener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
updateFocusView(v)
}
override fun onViewDetachedFromWindow(v: View) { object TvFocus {
// removes the focus view but not the listener as updateFocusView(null) will remove the listener data class FocusTarget(
focusOutline.get()?.isVisible = false val width: Int,
} val height: Int,
} val x: Float,
val y: Float,
private fun updateFocusView(newFocus: View?) { ) {
val focusOutline = focusOutline.get() ?: return companion object {
lastFocus.get()?.removeOnLayoutChangeListener(layoutListener) fun lerp(a: FocusTarget, b: FocusTarget, lerp: Float): FocusTarget {
lastFocus.get()?.removeOnAttachStateChangeListener(attachListener) val ilerp = 1 - lerp
val wasGone = focusOutline.isGone return FocusTarget(
width = (a.width * ilerp + b.width * lerp).toInt(),
val visible = height = (a.height * ilerp + b.height * lerp).toInt(),
newFocus != null && newFocus.measuredHeight > 0 && newFocus.measuredWidth > 0 && newFocus.isShown && newFocus.tag != "tv_no_focus_tag" x = a.x * ilerp + b.x * lerp,
focusOutline.isVisible = visible y = a.y * ilerp + b.y * lerp
if (newFocus != null) {
lastFocus = WeakReference(newFocus)
val out = IntArray(2)
newFocus.getLocationInWindow(out)
val (x, y) = out
// out of bounds = 0,0
if(x == 0 && y == 0) {
focusOutline.isVisible = false
}
/*(newFocus.parent as? RecyclerView)?.let { recycle ->
println("PARET IS RECYLE")
val position = recycle.getChildAdapterPosition(newFocus)
recycle.scrollToPosition(position)
(recycle.layoutManager as? GridLayoutManager)?.let {
if(it.orientation == LinearLayout.HORIZONTAL) {
println("SCROLL")
}
}
}*/
newFocus.addOnLayoutChangeListener(layoutListener)
newFocus.addOnAttachStateChangeListener(attachListener)
// val set = AnimationSet(true)
if(!wasGone) {
(focusOutline.parent as? ViewGroup)?.let {
TransitionManager.endTransitions(it)
TransitionManager.beginDelayedTransition(
it,
TransitionSet().addTransition(ChangeBounds())
.addTransition(ChangeTransform())
.setDuration(100)
) )
} }
} }
// ObjectAnimator.ofFloat(focusOutline, "translationX",focusOutline.translationX, x.toFloat() }
var last: FocusTarget = FocusTarget(0, 0, 0.0f, 0.0f)
var current: FocusTarget = FocusTarget(0, 0, 0.0f, 0.0f)
focusOutline.layoutParams = focusOutline.layoutParams?.apply { var focusOutline: WeakReference<View> = WeakReference(null)
width = newFocus.measuredWidth var lastFocus: WeakReference<View> = WeakReference(null)
height = newFocus.measuredHeight private val layoutListener: View.OnLayoutChangeListener =
View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
updateFocusView(
v, same = true
)
}
private val attachListener: View.OnAttachStateChangeListener =
object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
updateFocusView(v)
}
override fun onViewDetachedFromWindow(v: View) {
// removes the focus view but not the listener as updateFocusView(null) will remove the listener
focusOutline.get()?.isVisible = false
}
}
private fun setTargetPosition(target: FocusTarget) {
focusOutline.get()?.apply {
layoutParams = layoutParams?.apply {
width = target.width
height = target.height
}
translationX = target.x
translationY = target.y
}
}
private var animator: ValueAnimator? = null
@MainThread
fun updateFocusView(newFocus: View?, same: Boolean = false) {
val focusOutline = focusOutline.get() ?: return
lastFocus.get()?.apply {
removeOnLayoutChangeListener(layoutListener)
removeOnAttachStateChangeListener(attachListener)
}
val wasGone = focusOutline.isGone
val visible =
newFocus != null && newFocus.measuredHeight > 0 && newFocus.measuredWidth > 0 && newFocus.isShown && newFocus.tag != "tv_no_focus_tag"
focusOutline.isVisible = visible
if (newFocus != null) {
lastFocus = WeakReference(newFocus)
val out = IntArray(2)
newFocus.getLocationInWindow(out)
val (screenX, screenY) = out
var (x,y) = screenX.toFloat() to screenY.toFloat()
val (currentX, currentY) = focusOutline.translationX to focusOutline.translationY
// println(">><<< $x $y $currentX $currentY")
if(!newFocus.isLtr()) {
x = x - focusOutline.rootView.width + newFocus.measuredWidth
}
// out of bounds = 0,0
if (screenX == 0 && screenY == 0) {
focusOutline.isVisible = false
}
newFocus.addOnLayoutChangeListener(layoutListener)
newFocus.addOnAttachStateChangeListener(attachListener)
val start = FocusTarget(
x = currentX,
y = currentY,
width = focusOutline.measuredWidth,
height = focusOutline.measuredHeight
)
val end = FocusTarget(
x = x,
y = y,
width = newFocus.measuredWidth,
height = newFocus.measuredHeight
)
// if they are the same within then snap, aka scrolling
val deltaMin = 50.toPx
if (start.width == end.width && start.height == end.height && (start.x - end.x).absoluteValue < deltaMin && (start.y - end.y).absoluteValue < deltaMin) {
animator?.cancel()
last = start
current = end
setTargetPosition(end)
return
}
// if running then "reuse"
if (animator?.isRunning == true) {
current = end
return
} else {
animator?.cancel()
}
last = start
current = end
// if previously gone, then tp
if (wasGone) {
setTargetPosition(current)
return
}
// animate between a and b
animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
startDelay = 0
duration = 100
addUpdateListener { animation ->
val animatedValue = animation.animatedValue as Float
val target = FocusTarget.lerp(last, current, minOf(animatedValue, 1.0f))
setTargetPosition(target)
}
start()
}
// post check
if (!same) {
newFocus.postDelayed({
updateFocusView(lastFocus.get(), same = true)
}, 200)
}
/*
the following is working, but somewhat bad code code
if (!wasGone) {
(focusOutline.parent as? ViewGroup)?.let {
TransitionManager.endTransitions(it)
TransitionManager.beginDelayedTransition(
it,
TransitionSet().addTransition(ChangeBounds())
.addTransition(ChangeTransform())
.setDuration(100)
)
}
}
focusOutline.layoutParams = focusOutline.layoutParams?.apply {
width = newFocus.measuredWidth
height = newFocus.measuredHeight
}
focusOutline.translationX = x.toFloat()
focusOutline.translationY = y.toFloat()*/
} }
focusOutline.translationX = x.toFloat()
focusOutline.translationY = y.toFloat()
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
app.initClient(this) app.initClient(this)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
@ -872,13 +989,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
if (isTvSettings()) { if (isTvSettings()) {
val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false) val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false)
setContentView(newLocalBinding.root) setContentView(newLocalBinding.root)
focusOutline = WeakReference(newLocalBinding.focusOutline) TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus -> newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
// println("refocus $oldFocus -> $newFocus") // println("refocus $oldFocus -> $newFocus")
updateFocusView(newFocus) TvFocus.updateFocusView(newFocus)
} }
newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener { newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener {
updateFocusView(lastFocus.get()) TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true)
} }
ActivityMainBinding.bind(newLocalBinding.root) // this may crash ActivityMainBinding.bind(newLocalBinding.root) // this may crash
@ -1230,16 +1347,21 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}*/ }*/
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
var providersAndroidManifestString = "Current androidmanifest should be:\n" try {
for (api in allProviders) { var providersAndroidManifestString = "Current androidmanifest should be:\n"
providersAndroidManifestString += "<data android:scheme=\"https\" android:host=\"${ for (api in allProviders) {
api.mainUrl.removePrefix( providersAndroidManifestString += "<data android:scheme=\"https\" android:host=\"${
"https://" api.mainUrl.removePrefix(
) "https://"
}\" android:pathPrefix=\"/\"/>\n" )
}\" android:pathPrefix=\"/\"/>\n"
}
println(providersAndroidManifestString)
} catch (t: Throwable) {
logError(t)
} }
println(providersAndroidManifestString)
} }
handleAppIntent(intent) handleAppIntent(intent)

View File

@ -177,7 +177,7 @@ open class ParentItemAdapter(
).apply { ).apply {
isHorizontal = info.isHorizontalImages isHorizontal = info.isHorizontalImages
} }
recyclerView.setLinearListLayout() //recyclerView.setLinearListLayout()
} }
} }
@ -192,7 +192,7 @@ open class ParentItemAdapter(
isHorizontal = info.isHorizontalImages isHorizontal = info.isHorizontalImages
hasNext = expand.hasNext hasNext = expand.hasNext
} }
recyclerView.setLinearListLayout() // recyclerView.setLinearListLayout()
title.text = info.name title.text = info.name
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {

View File

@ -19,6 +19,7 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueT
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import java.util.* import java.util.*
@ -48,7 +49,8 @@ const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16
const val ACTION_PLAY_EPISODE_IN_MPV = 17 const val ACTION_PLAY_EPISODE_IN_MPV = 17
const val ACTION_MARK_AS_WATCHED = 18 const val ACTION_MARK_AS_WATCHED = 18
const val TV_EP_SIZE_LARGE = 400
const val TV_EP_SIZE_SMALL = 300
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode) data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
class EpisodeAdapter( class EpisodeAdapter(
@ -172,7 +174,7 @@ class EpisodeAdapter(
localCard = card localCard = card
val setWidth = val setWidth =
if (isTvSettings()) ViewGroup.LayoutParams.WRAP_CONTENT else ViewGroup.LayoutParams.MATCH_PARENT if (isTvSettings()) TV_EP_SIZE_LARGE.toPx else ViewGroup.LayoutParams.MATCH_PARENT
binding.episodeLinHolder.layoutParams.width = setWidth binding.episodeLinHolder.layoutParams.width = setWidth
binding.episodeHolderLarge.layoutParams.width = setWidth binding.episodeHolderLarge.layoutParams.width = setWidth
@ -293,7 +295,7 @@ class EpisodeAdapter(
binding.episodeHolder.layoutParams.apply { binding.episodeHolder.layoutParams.apply {
width = width =
if (isTvSettings()) ViewGroup.LayoutParams.WRAP_CONTENT else ViewGroup.LayoutParams.MATCH_PARENT if (isTvSettings()) TV_EP_SIZE_SMALL.toPx else ViewGroup.LayoutParams.MATCH_PARENT
} }
binding.apply { binding.apply {

View File

@ -2,15 +2,19 @@ package com.lagradost.cloudstream3.ui.result
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import android.view.View.LAYOUT_DIRECTION_LTR
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.AppUtils.isLtr
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) { fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) {
if (this == null) return if (this == null) return
this.layoutManager = this.layoutManager =
this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } } this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } }
?: this.layoutManager // ?: this.layoutManager
} }
open class LinearListLayout(context: Context?) : open class LinearListLayout(context: Context?) :
@ -56,7 +60,7 @@ open class LinearListLayout(context: Context?) :
startSmoothScroll(linearSmoothScroller) startSmoothScroll(linearSmoothScroller)
}*/ }*/
override fun onInterceptFocusSearch(focused: View, direction: Int): View? { override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
val dir = if (orientation == HORIZONTAL) { var dir = if (orientation == HORIZONTAL) {
if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) { if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) {
// This scrolls the recyclerview before doing focus search, which // This scrolls the recyclerview before doing focus search, which
// allows the focus search to work better. // allows the focus search to work better.
@ -71,6 +75,9 @@ open class LinearListLayout(context: Context?) :
if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null
if (direction == View.FOCUS_DOWN) 1 else -1 if (direction == View.FOCUS_DOWN) 1 else -1
} }
if(this.isLayoutRTL) {
dir = -dir
}
return try { return try {
getPosition(getCorrectParent(focused))?.let { position -> getPosition(getCorrectParent(focused))?.let { position ->

View File

@ -62,6 +62,8 @@ import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.isLtr
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink

View File

@ -14,6 +14,7 @@ import android.view.animation.DecelerateInterpolator
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -30,6 +31,7 @@ import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.result.ResultFragment.getStoredData import com.lagradost.cloudstream3.ui.result.ResultFragment.getStoredData
@ -38,6 +40,8 @@ import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppUtils.isLtr
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
@ -254,6 +258,12 @@ class ResultFragmentTv : Fragment() {
resultEpisodesShow.onFocusChangeListener = rightListener resultEpisodesShow.onFocusChangeListener = rightListener
resultDescription.onFocusChangeListener = leftListener resultDescription.onFocusChangeListener = leftListener
resultBookmarkButton.onFocusChangeListener = leftListener resultBookmarkButton.onFocusChangeListener = leftListener
resultEpisodesShow.setOnClickListener {
// toggle, to make it more touch accessable just in case someone thinks that a
// tv layout is better but is using a touch device
toggleEpisodes(!episodeHolderTv.isVisible)
}
redirectToPlay.setOnFocusChangeListener { _, hasFocus -> redirectToPlay.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) return@setOnFocusChangeListener if (!hasFocus) return@setOnFocusChangeListener
toggleEpisodes(false) toggleEpisodes(false)
@ -272,6 +282,12 @@ class ResultFragmentTv : Fragment() {
} }
} }
} }
// parallax on background
resultFinishLoading.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
backgroundPosterHolder.translationY = -scrollY.toFloat() * 0.8f
})
redirectToEpisodes.setOnFocusChangeListener { _, hasFocus -> redirectToEpisodes.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) return@setOnFocusChangeListener if (!hasFocus) return@setOnFocusChangeListener
toggleEpisodes(true) toggleEpisodes(true)
@ -290,10 +306,10 @@ class ResultFragmentTv : Fragment() {
} }
} }
resultEpisodes.layoutManager = resultEpisodes.setLinearListLayout(false)/*.layoutManager =
LinearListLayout(resultEpisodes.context).apply { LinearListLayout(resultEpisodes.context, resultEpisodes.isRtl()).apply {
setVertical() setVertical()
} }*/
resultReloadConnectionerror.setOnClickListener { resultReloadConnectionerror.setOnClickListener {
viewModel.load( viewModel.load(

View File

@ -20,6 +20,9 @@ import android.os.*
import android.provider.MediaStore import android.provider.MediaStore
import android.text.Spanned import android.text.Spanned
import android.util.Log import android.util.Log
import android.view.View
import android.view.View.LAYOUT_DIRECTION_LTR
import android.view.View.LAYOUT_DIRECTION_RTL
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@ -90,6 +93,9 @@ object AppUtils {
return if (layoutManager == null || adapter == null) false else layoutManager.findLastCompletelyVisibleItemPosition() < adapter.itemCount - 7 // bit more than 1 to make it more seamless return if (layoutManager == null || adapter == null) false else layoutManager.findLastCompletelyVisibleItemPosition() < adapter.itemCount - 7 // bit more than 1 to make it more seamless
} }
fun View.isLtr() = this.layoutDirection == LAYOUT_DIRECTION_LTR
fun View.isRtl() = this.layoutDirection == LAYOUT_DIRECTION_RTL
fun BottomSheetDialog?.ownHide() { fun BottomSheetDialog?.ownHide() {
this?.hide() this?.hide()
} }

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@drawable/outline_less" /> <!-- focused -->
<!--<item android:state_focused="true" android:drawable="@drawable/outline_less" />--> <!-- focused -->
</selector> </selector>

View File

@ -83,6 +83,7 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<ImageView <ImageView
android:elevation="999999999dp"
android:focusable="false" android:focusable="false"
android:focusableInTouchMode="false" android:focusableInTouchMode="false"
android:clickable="false" android:clickable="false"

View File

@ -126,6 +126,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
<FrameLayout <FrameLayout
android:id="@+id/background_poster_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="200dp" android:layout_height="200dp"
android:visibility="visible"> android:visibility="visible">
@ -152,12 +153,12 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/result_finish_loading"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<LinearLayout <LinearLayout
android:id="@+id/result_finish_loading"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
@ -415,6 +416,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/result_tag" android:id="@+id/result_tag"
style="@style/ChipParent" style="@style/ChipParent"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
@ -479,8 +481,8 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:fadingEdge="horizontal" android:fadingEdge="horizontal"
android:focusable="false" android:focusable="false"
android:focusableInTouchMode="false" android:focusableInTouchMode="false"
android:nextFocusUp="@id/result_back" android:nextFocusUp="@id/result_episodes_show"
android:nextFocusDown="@id/result_play_movie" android:nextFocusDown="@id/result_recommendations_filter_selection"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingTop="5dp" android:paddingTop="5dp"
android:requiresFadingEdge="horizontal" android:requiresFadingEdge="horizontal"
@ -502,9 +504,9 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:nextFocusUp="@id/result_episodes" android:nextFocusUp="@id/result_cast_items"
android:nextFocusDown="@id/result_recommendations_list" android:nextFocusDown="@id/result_recommendations_list"
android:orientation="horizontal" android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="2" tools:itemCount="2"
@ -525,7 +527,6 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/primaryBlackBackground"
android:clipToPadding="false" android:clipToPadding="false"
android:descendantFocusability="afterDescendants" android:descendantFocusability="afterDescendants"
android:nextFocusUp="@id/result_recommendations_filter_selection" android:nextFocusUp="@id/result_recommendations_filter_selection"

View File

@ -122,18 +122,17 @@
tools:listitem="@layout/homepage_parent" /> tools:listitem="@layout/homepage_parent" />
<FrameLayout <FrameLayout
android:background="?attr/primaryBlackBackground"
android:id="@+id/search_history_holder" android:id="@+id/search_history_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:paddingStart="@dimen/navbar_width" android:layout_marginStart="@dimen/navbar_width"
android:id="@+id/search_history_recycler" android:id="@+id/search_history_recycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/primaryBlackBackground"
android:descendantFocusability="afterDescendants" android:descendantFocusability="afterDescendants"
android:nextFocusLeft="@id/nav_rail_view" android:nextFocusLeft="@id/nav_rail_view"
android:visibility="visible" android:visibility="visible"
@ -141,6 +140,7 @@
tools:listitem="@layout/search_history_item" /> tools:listitem="@layout/search_history_item" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:layout_marginStart="@dimen/navbar_width"
android:id="@+id/search_clear_call_history" android:id="@+id/search_clear_call_history"
style="@style/BlackButton" style="@style/BlackButton"
android:layout_gravity="bottom" android:layout_gravity="bottom"

View File

@ -6,10 +6,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/outline_drawable_less" android:background="@drawable/outline_drawable_less"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:nextFocusRight="@id/home_history_remove" android:nextFocusRight="@id/home_history_remove"
android:orientation="horizontal"> android:orientation="horizontal">
<!-- android:foreground="?android:attr/selectableItemBackgroundBorderless"
-->
<TextView <TextView
android:id="@+id/home_history_title" android:id="@+id/home_history_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -29,7 +30,6 @@
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:background="@drawable/outline_drawable_less" android:background="@drawable/outline_drawable_less"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:nextFocusLeft="@id/home_history_tab" android:nextFocusLeft="@id/home_history_tab"
android:padding="10dp" android:padding="10dp"

View File

@ -104,7 +104,7 @@
</style> </style>
<style name="ChipParent"> <style name="ChipParent">
<item name="chipSpacingVertical">5dp</item> <item name="chipSpacingVertical">-5dp</item>
<item name="chipSpacingHorizontal">5dp</item> <item name="chipSpacingHorizontal">5dp</item>
<item name="textColor">?attr/textColor</item> <item name="textColor">?attr/textColor</item>
</style> </style>