forked from recloudstream/cloudstream
android TV fix
This commit is contained in:
parent
53288cfaf1
commit
b13c1eac01
9 changed files with 150 additions and 25 deletions
|
@ -145,11 +145,75 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class FocusDirection {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getNextFocus(view: View?, direction: FocusDirection, depth: Int = 0): Int? {
|
||||||
|
if (view == null || depth >= 10) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val nextId = when (direction) {
|
||||||
|
FocusDirection.Left -> {
|
||||||
|
view.nextFocusLeftId
|
||||||
|
}
|
||||||
|
FocusDirection.Up -> {
|
||||||
|
view.nextFocusUpId
|
||||||
|
}
|
||||||
|
FocusDirection.Right -> {
|
||||||
|
view.nextFocusRightId
|
||||||
|
}
|
||||||
|
FocusDirection.Down -> {
|
||||||
|
view.nextFocusDownId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (nextId != -1) {
|
||||||
|
val next = findViewById<View?>(nextId)
|
||||||
|
//println("NAME: ${next.accessibilityClassName} | ${next?.isShown}" )
|
||||||
|
|
||||||
|
if (next?.isShown == false) {
|
||||||
|
getNextFocus(next, direction, depth + 1)
|
||||||
|
} else {
|
||||||
|
if (depth == 0) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
nextId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
||||||
event?.keyCode?.let { keyCode ->
|
event?.keyCode?.let { keyCode ->
|
||||||
when (event.action) {
|
when (event.action) {
|
||||||
ACTION_DOWN -> {
|
ACTION_DOWN -> {
|
||||||
|
if (currentFocus != null) {
|
||||||
|
val next = when (keyCode) {
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT -> getNextFocus(currentFocus, FocusDirection.Left)
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT -> getNextFocus(currentFocus, FocusDirection.Right)
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP -> getNextFocus(currentFocus, FocusDirection.Up)
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN -> getNextFocus(currentFocus, FocusDirection.Down)
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next != null && next != -1) {
|
||||||
|
val nextView = findViewById<View?>(next)
|
||||||
|
if(nextView != null) {
|
||||||
|
nextView.requestFocus()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
when (keyCode) {
|
when (keyCode) {
|
||||||
|
|
||||||
KeyEvent.KEYCODE_DPAD_CENTER -> {
|
KeyEvent.KEYCODE_DPAD_CENTER -> {
|
||||||
println("DPAD PRESSED $currentFocus")
|
println("DPAD PRESSED $currentFocus")
|
||||||
if (currentFocus is SearchView || currentFocus is SearchView.SearchAutoComplete) {
|
if (currentFocus is SearchView || currentFocus is SearchView.SearchAutoComplete) {
|
||||||
|
@ -158,7 +222,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//println("Keycode: $keyCode")
|
//println("Keycode: $keyCode")
|
||||||
//showToast(
|
//showToast(
|
||||||
// this,
|
// this,
|
||||||
|
@ -169,7 +233,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(keyEventListener?.invoke(event) == true) {
|
if (keyEventListener?.invoke(event) == true) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.dispatchKeyEvent(event)
|
return super.dispatchKeyEvent(event)
|
||||||
|
|
|
@ -12,13 +12,19 @@ import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
class HomeChildItemAdapter(
|
class HomeChildItemAdapter(
|
||||||
var cardList: List<SearchResponse>,
|
var cardList: List<SearchResponse>,
|
||||||
val layout: Int = R.layout.home_result_grid,
|
val layout: Int = R.layout.home_result_grid,
|
||||||
private val clickCallback: (SearchClickCallback) -> Unit
|
private val nextFocusUp: Int? = null,
|
||||||
|
private val nextFocusDown: Int? = null,
|
||||||
|
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||||
) :
|
) :
|
||||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return CardViewHolder(
|
return CardViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback, itemCount
|
LayoutInflater.from(parent.context).inflate(layout, parent, false),
|
||||||
|
clickCallback,
|
||||||
|
itemCount,
|
||||||
|
nextFocusUp,
|
||||||
|
nextFocusDown
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,19 +45,23 @@ class HomeChildItemAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
class CardViewHolder
|
class CardViewHolder
|
||||||
constructor(itemView: View, private val clickCallback: (SearchClickCallback) -> Unit, val itemCount: Int) :
|
constructor(
|
||||||
|
itemView: View, private val clickCallback: (SearchClickCallback) -> Unit, private val itemCount: Int,
|
||||||
|
private val nextFocusUp: Int? = null,
|
||||||
|
private val nextFocusDown: Int? = null,
|
||||||
|
) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
fun bind(card: SearchResponse, index: Int) {
|
fun bind(card: SearchResponse, index: Int) {
|
||||||
|
|
||||||
// TV focus fixing
|
// TV focus fixing
|
||||||
val nextFocusBehavior = when(index){
|
val nextFocusBehavior = when (index) {
|
||||||
0 -> true
|
0 -> true
|
||||||
itemCount - 1 -> false
|
itemCount - 1 -> false
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchResultBuilder.bind(clickCallback, card, itemView, nextFocusBehavior)
|
SearchResultBuilder.bind(clickCallback, card, itemView, nextFocusBehavior, nextFocusUp, nextFocusDown)
|
||||||
itemView.tag = index
|
itemView.tag = index
|
||||||
//val ani = ScaleAnimation(0.9f, 1.0f, 0.9f, 1f)
|
//val ani = ScaleAnimation(0.9f, 1.0f, 0.9f, 1f)
|
||||||
//ani.fillAfter = true
|
//ani.fillAfter = true
|
||||||
|
|
|
@ -35,6 +35,7 @@ import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
|
||||||
import com.lagradost.cloudstream3.ui.search.*
|
import com.lagradost.cloudstream3.ui.search.*
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
|
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
|
@ -212,7 +213,12 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
val randomSize = items.size
|
val randomSize = items.size
|
||||||
home_main_poster_recyclerview.adapter =
|
home_main_poster_recyclerview.adapter =
|
||||||
HomeChildItemAdapter(items, R.layout.home_result_big_grid) { callback ->
|
HomeChildItemAdapter(
|
||||||
|
items,
|
||||||
|
R.layout.home_result_big_grid,
|
||||||
|
nextFocusUp = home_main_poster_recyclerview.nextFocusUpId,
|
||||||
|
nextFocusDown = home_main_poster_recyclerview.nextFocusDownId
|
||||||
|
) { callback ->
|
||||||
handleSearchClickCallback(activity, callback)
|
handleSearchClickCallback(activity, callback)
|
||||||
}
|
}
|
||||||
home_main_poster_recyclerview.post {
|
home_main_poster_recyclerview.post {
|
||||||
|
@ -339,7 +345,11 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
home_bookmarked_child_recyclerview.adapter = HomeChildItemAdapter(ArrayList()) { callback ->
|
home_bookmarked_child_recyclerview.adapter = HomeChildItemAdapter(
|
||||||
|
ArrayList(),
|
||||||
|
nextFocusUp = home_bookmarked_child_recyclerview?.nextFocusUpId,
|
||||||
|
nextFocusDown = home_bookmarked_child_recyclerview?.nextFocusDownId
|
||||||
|
) { callback ->
|
||||||
if (callback.action == SEARCH_ACTION_SHOW_METADATA) {
|
if (callback.action == SEARCH_ACTION_SHOW_METADATA) {
|
||||||
val id = callback.card.id
|
val id = callback.card.id
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
|
@ -355,7 +365,11 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
home_watch_child_recyclerview.adapter = HomeChildItemAdapter(ArrayList()) { callback ->
|
home_watch_child_recyclerview.adapter = HomeChildItemAdapter(
|
||||||
|
ArrayList(),
|
||||||
|
nextFocusUp = home_watch_child_recyclerview?.nextFocusUpId,
|
||||||
|
nextFocusDown = home_watch_child_recyclerview?.nextFocusDownId
|
||||||
|
) { callback ->
|
||||||
if (callback.action == SEARCH_ACTION_SHOW_METADATA) {
|
if (callback.action == SEARCH_ACTION_SHOW_METADATA) {
|
||||||
val id = callback.card.id
|
val id = callback.card.id
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
|
@ -423,6 +437,16 @@ class HomeFragment : Fragment() {
|
||||||
// nice profile pic on homepage
|
// nice profile pic on homepage
|
||||||
home_profile_picture_holder?.isVisible = false
|
home_profile_picture_holder?.isVisible = false
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
|
// just in case
|
||||||
|
if (ctx.isTvSettings()) {
|
||||||
|
home_change_api_loading?.isFocusable = true
|
||||||
|
home_change_api_loading?.isFocusableInTouchMode = true
|
||||||
|
home_change_api?.isFocusable = true
|
||||||
|
home_change_api?.isFocusableInTouchMode = true
|
||||||
|
home_bookmark_select?.isFocusable = true
|
||||||
|
home_bookmark_select?.isFocusableInTouchMode = true
|
||||||
|
}
|
||||||
|
|
||||||
for (syncApi in OAuth2API.OAuth2Apis) {
|
for (syncApi in OAuth2API.OAuth2Apis) {
|
||||||
val login = syncApi.loginInfo(ctx)
|
val login = syncApi.loginInfo(ctx)
|
||||||
val pic = login?.profilePicture
|
val pic = login?.profilePicture
|
||||||
|
@ -433,5 +457,7 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -47,7 +47,12 @@ class ParentItemAdapter(
|
||||||
private val moreInfo: FrameLayout = itemView.home_child_more_info
|
private val moreInfo: FrameLayout = itemView.home_child_more_info
|
||||||
fun bind(info: HomePageList) {
|
fun bind(info: HomePageList) {
|
||||||
title.text = info.name
|
title.text = info.name
|
||||||
recyclerView.adapter = HomeChildItemAdapter(info.list, clickCallback = clickCallback)
|
recyclerView.adapter = HomeChildItemAdapter(
|
||||||
|
info.list,
|
||||||
|
clickCallback = clickCallback,
|
||||||
|
nextFocusUp = recyclerView.nextFocusUpId,
|
||||||
|
nextFocusDown = recyclerView.nextFocusDownId
|
||||||
|
)
|
||||||
(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
|
(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
|
||||||
|
|
||||||
moreInfo.setOnClickListener {
|
moreInfo.setOnClickListener {
|
||||||
|
|
|
@ -1018,7 +1018,7 @@ class PlayerFragment : Fragment() {
|
||||||
|
|
||||||
private fun handleKeyEvent(event: KeyEvent): Boolean {
|
private fun handleKeyEvent(event: KeyEvent): Boolean {
|
||||||
event.keyCode.let { keyCode ->
|
event.keyCode.let { keyCode ->
|
||||||
when (event.action) {
|
when (keyCode) {
|
||||||
// don't allow dpad move when hidden
|
// don't allow dpad move when hidden
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
|
@ -1032,6 +1032,17 @@ class PlayerFragment : Fragment() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// netflix capture back and hide ~monke
|
||||||
|
KeyEvent.KEYCODE_BACK -> {
|
||||||
|
if (isShowing) {
|
||||||
|
onClickChange()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (event.action) {
|
||||||
KeyEvent.ACTION_DOWN -> {
|
KeyEvent.ACTION_DOWN -> {
|
||||||
when (keyCode) {
|
when (keyCode) {
|
||||||
KeyEvent.KEYCODE_DPAD_CENTER -> {
|
KeyEvent.KEYCODE_DPAD_CENTER -> {
|
||||||
|
|
|
@ -38,7 +38,7 @@ class SearchAdapter(
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is CardViewHolder -> {
|
is CardViewHolder -> {
|
||||||
holder.bind(cardList[position])
|
holder.bind(cardList[position], position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ class SearchAdapter(
|
||||||
private val compactView = itemView.context.getGridIsCompact()
|
private val compactView = itemView.context.getGridIsCompact()
|
||||||
private val coverHeight: Int = if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt()
|
private val coverHeight: Int = if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt()
|
||||||
|
|
||||||
fun bind(card: SearchResponse) {
|
fun bind(card: SearchResponse, position: Int) {
|
||||||
if (!compactView) {
|
if (!compactView) {
|
||||||
cardView.apply {
|
cardView.apply {
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
|
@ -69,7 +69,7 @@ class SearchAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchResultBuilder.bind(clickCallback, card, itemView,)
|
SearchResultBuilder.bind(clickCallback, card, itemView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,9 @@ object SearchResultBuilder {
|
||||||
clickCallback: (SearchClickCallback) -> Unit,
|
clickCallback: (SearchClickCallback) -> Unit,
|
||||||
card: SearchResponse,
|
card: SearchResponse,
|
||||||
itemView: View,
|
itemView: View,
|
||||||
nextFocusBehavior: Boolean? = null
|
nextFocusBehavior: Boolean? = null,
|
||||||
|
nextFocusUp: Int? = null,
|
||||||
|
nextFocusDown: Int? = null,
|
||||||
) {
|
) {
|
||||||
val cardView: ImageView = itemView.imageView
|
val cardView: ImageView = itemView.imageView
|
||||||
val cardText: TextView? = itemView.imageText
|
val cardText: TextView? = itemView.imageText
|
||||||
|
@ -57,6 +59,14 @@ object SearchResultBuilder {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(nextFocusUp != null) {
|
||||||
|
bg.nextFocusUpId = nextFocusUp
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nextFocusDown != null) {
|
||||||
|
bg.nextFocusDownId = nextFocusDown
|
||||||
|
}
|
||||||
|
|
||||||
when (nextFocusBehavior) {
|
when (nextFocusBehavior) {
|
||||||
true -> bg.nextFocusLeftId = bg.id
|
true -> bg.nextFocusLeftId = bg.id
|
||||||
false -> bg.nextFocusRightId = bg.id
|
false -> bg.nextFocusRightId = bg.id
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
app:iconifiedByDefault="false"
|
app:iconifiedByDefault="false"
|
||||||
tools:ignore="RtlSymmetry">
|
tools:ignore="RtlSymmetry">
|
||||||
|
<requestFocus/>
|
||||||
|
|
||||||
<androidx.core.widget.ContentLoadingProgressBar
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
android:id="@+id/search_loading_bar"
|
android:id="@+id/search_loading_bar"
|
||||||
|
@ -75,7 +76,6 @@
|
||||||
android:layout_gravity="end|center_vertical"
|
android:layout_gravity="end|center_vertical"
|
||||||
app:tint="?attr/textColor"
|
app:tint="?attr/textColor"
|
||||||
android:contentDescription="@string/change_providers_img_des">
|
android:contentDescription="@string/change_providers_img_des">
|
||||||
<requestFocus/>
|
|
||||||
</ImageView>
|
</ImageView>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:nextFocusDown="@id/home_child_recyclerview"
|
|
||||||
|
|
||||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
android:id="@+id/home_child_more_info"
|
android:id="@+id/home_child_more_info"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
|
@ -32,6 +30,7 @@
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:nextFocusUp="@id/home_child_more_info"
|
||||||
android:paddingHorizontal="5dp"
|
android:paddingHorizontal="5dp"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue