From b120a7bce206a1c7f537dc686446be3f0255eac6 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Tue, 10 Oct 2023 18:16:35 +0300 Subject: [PATCH] Library on TV (#663) * implementation for Library on TV --- .../cloudstream3/ExampleInstrumentedTest.kt | 4 + .../lagradost/cloudstream3/MainActivity.kt | 6 +- .../ui/library/LibraryFragment.kt | 98 +++++++-- .../ui/library/ViewpagerAdapter.kt | 5 +- app/src/main/res/layout/fragment_library.xml | 17 +- .../main/res/layout/fragment_library_tv.xml | 200 ++++++++++++++++++ .../res/layout/library_viewpager_page.xml | 2 + 7 files changed, 305 insertions(+), 27 deletions(-) create mode 100644 app/src/main/res/layout/fragment_library_tv.xml diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index df41ef91..a84b2457 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -9,6 +9,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.viewbinding.ViewBinding import com.lagradost.cloudstream3.databinding.FragmentHomeBinding import com.lagradost.cloudstream3.databinding.FragmentHomeTvBinding +import com.lagradost.cloudstream3.databinding.FragmentLibraryBinding +import com.lagradost.cloudstream3.databinding.FragmentLibraryTvBinding import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding import com.lagradost.cloudstream3.databinding.FragmentPlayerTvBinding import com.lagradost.cloudstream3.databinding.FragmentResultBinding @@ -120,6 +122,8 @@ class ExampleInstrumentedTest { testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent) testAllLayouts(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent) + testAllLayouts(activity, R.layout.fragment_library_tv, R.layout.fragment_library) + testAllLayouts(activity, R.layout.fragment_library_tv, R.layout.fragment_library) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 5595c377..f9fff88c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -543,9 +543,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { navRailView.isVisible = isNavVisible && landscape // Hide library on TV since it is not supported yet :( - val isTrueTv = isTrueTvSettings() - navView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv - navRailView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv + //val isTrueTv = isTrueTvSettings() + //navView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv + //navRailView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv // Hide downloads on TV //navView.menu.findItem(R.id.navigation_downloads)?.isVisible = !isTrueTv diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index a3cc16c9..85f0aedd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -1,38 +1,52 @@ package com.lagradost.cloudstream3.ui.library +import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.res.Configuration import android.os.Bundle import android.os.Handler import android.os.Looper +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.ViewGroup.FOCUS_AFTER_DESCENDANTS +import android.view.ViewGroup.FOCUS_BLOCK_DESCENDANTS import android.view.animation.AlphaAnimation +import android.widget.LinearLayout +import android.widget.TextView +import android.widget.Toast import androidx.annotation.StringRes import androidx.appcompat.widget.SearchView +import androidx.core.view.allViews import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.viewpager2.widget.ViewPager2 +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.APIHolder.allProviders import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentLibraryBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.debugAssert +import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName +import com.lagradost.cloudstream3.ui.AutofitRecyclerView import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA +import com.lagradost.cloudstream3.ui.settings.SettingsFragment import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.reduceDragSensitivity @@ -80,9 +94,21 @@ class LibraryFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - val localBinding = FragmentLibraryBinding.inflate(inflater, container, false) - binding = localBinding - return localBinding.root + val layout = + if (SettingsFragment.isTvSettings()) R.layout.fragment_library_tv else R.layout.fragment_library + val root = inflater.inflate(layout, container, false) + binding = try { + FragmentLibraryBinding.bind(root) + } catch (t: Throwable) { + CommonActivity.showToast( + txt(R.string.unable_to_inflate, t.message ?: ""), + Toast.LENGTH_LONG + ) + logError(t) + null + } + + return root //return inflater.inflate(R.layout.fragment_library, container, false) } @@ -99,24 +125,16 @@ class LibraryFragment : Fragment() { super.onSaveInstanceState(outState) } + @SuppressLint("ResourceType", "CutPasteId") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) fixPaddingStatusbar(binding?.searchStatusBarPadding) - binding?.sortFab?.setOnClickListener { - val methods = libraryViewModel.sortingMethods.map { - txt(it.stringRes).asString(view.context) - } + binding?.sortFab?.setOnClickListener(sortChangeClickListener) + binding?.librarySort?.setOnClickListener(sortChangeClickListener) - activity?.showBottomDialog(methods, - libraryViewModel.sortingMethods.indexOf(libraryViewModel.currentSortingMethod), - txt(R.string.sort_by).asString(view.context), - false, - {}, - { - val method = libraryViewModel.sortingMethods[it] - libraryViewModel.sort(method) - }) + binding?.libraryRoot?.findViewById(R.id.search_src_text)?.apply { + tag = "tv_no_focus_tag" } binding?.mainSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { @@ -266,7 +284,10 @@ class LibraryFragment : Fragment() { // selects the whole list opener val savedListSelection = getKey("$currentAccount/$LIBRARY_FOLDER", syncName.name) - val savedSelection = getKey("$currentAccount/$LIBRARY_FOLDER", syncId).takeIf { + val savedSelection = getKey( + "$currentAccount/$LIBRARY_FOLDER", + syncId + ).takeIf { it?.openType != LibraryOpenerType.Default } ?: savedListSelection @@ -354,6 +375,12 @@ class LibraryFragment : Fragment() { } (viewpager.adapter as? ViewpagerAdapter)?.pages = pages + //fix focus on the viewpager itself + (viewpager.getChildAt(0) as RecyclerView).apply { + tag = "tv_no_focus_tag" + //isFocusable = false + } + // Using notifyItemRangeChanged keeps the animations when sorting viewpager.adapter?.notifyItemRangeChanged( 0, @@ -396,6 +423,9 @@ class LibraryFragment : Fragment() { viewpager, ) { tab, position -> tab.text = pages.getOrNull(position)?.title?.asStringNull(context) + tab.view.tag = "tv_no_focus_tag" + tab.view.nextFocusDownId = R.id.search_result_root + tab.view.setOnClickListener { val currentItem = binding?.viewpager?.currentItem ?: return@setOnClickListener @@ -418,17 +448,45 @@ class LibraryFragment : Fragment() { } } } - binding?.viewpager?.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + binding?.viewpager?.registerOnPageChangeCallback(object : + ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { - libraryViewModel.currentPage = position + val all = binding?.viewpager?.allViews?.toList() + ?.filterIsInstance() + + all?.forEach { view -> + view.isVisible = view.tag == position + view.isFocusable = view.tag == position + + if (view.tag == position) + view.descendantFocusability = FOCUS_AFTER_DESCENDANTS + else + view.descendantFocusability = FOCUS_BLOCK_DESCENDANTS + } + super.onPageSelected(position) } }) } - override fun onConfigurationChanged(newConfig: Configuration) { (binding?.viewpager?.adapter as? ViewpagerAdapter)?.rebind() super.onConfigurationChanged(newConfig) } + + private val sortChangeClickListener = View.OnClickListener { view -> + val methods = libraryViewModel.sortingMethods.map { + txt(it.stringRes).asString(view.context) + } + + activity?.showBottomDialog(methods, + libraryViewModel.sortingMethods.indexOf(libraryViewModel.currentSortingMethod), + txt(R.string.sort_by).asString(view.context), + false, + {}, + { + val method = libraryViewModel.sortingMethods[it] + libraryViewModel.sort(method) + }) + } } class MenuSearchView(context: Context) : SearchView(context) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt index 95fefcbe..76028487 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -25,7 +25,7 @@ class ViewpagerAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { is PageViewHolder -> { - holder.bind(pages[position], unbound.remove(position)) + holder.bind(pages[position], position, unbound.remove(position)) } } } @@ -43,7 +43,8 @@ class ViewpagerAdapter( inner class PageViewHolder(private val binding: LibraryViewpagerPageBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(page: SyncAPI.Page, rebind: Boolean) { + fun bind(page: SyncAPI.Page, position: Int, rebind: Boolean) { + binding.pageRecyclerview.tag = position binding.pageRecyclerview.apply { spanCount = this@PageViewHolder.itemView.context.getSpanCount() ?: 3 diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 985d055d..879ddbd9 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -41,6 +41,20 @@ android:src="@drawable/ic_baseline_extension_24" app:tint="?attr/textColor" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/library_viewpager_page.xml b/app/src/main/res/layout/library_viewpager_page.xml index 7d278cff..aa9745fb 100644 --- a/app/src/main/res/layout/library_viewpager_page.xml +++ b/app/src/main/res/layout/library_viewpager_page.xml @@ -5,5 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" + android:focusable="false" + android:tag="tv_no_focus_tag" tools:listitem="@layout/home_result_grid_expanded" />