mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
fixed crash + fixed lib + fixed preview
This commit is contained in:
parent
8d5b73495d
commit
eb60be54ed
8 changed files with 169 additions and 114 deletions
|
@ -12,6 +12,7 @@ import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
open class ViewHolderState<T>(val view: ViewBinding) : ViewHolder(view.root) {
|
open class ViewHolderState<T>(val view: ViewBinding) : ViewHolder(view.root) {
|
||||||
open fun save(): T? = null
|
open fun save(): T? = null
|
||||||
|
@ -27,6 +28,8 @@ class StateViewModel : ViewModel() {
|
||||||
val layoutManagerStates = hashMapOf<Int, HashMap<Int, Any?>>()
|
val layoutManagerStates = hashMapOf<Int, HashMap<Int, Any?>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class NoStateAdapter<T : Any>(fragment: Fragment) : BaseAdapter<T, Any>(fragment, 0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BaseAdapter is a persistent state stored adapter that supports headers and footers.
|
* BaseAdapter is a persistent state stored adapter that supports headers and footers.
|
||||||
* This should be used for restoring eg scroll or focus related to a view when it is recreated.
|
* This should be used for restoring eg scroll or focus related to a view when it is recreated.
|
||||||
|
@ -83,7 +86,8 @@ abstract class BaseAdapter<
|
||||||
)
|
)
|
||||||
|
|
||||||
fun submitList(list: List<T>?) {
|
fun submitList(list: List<T>?) {
|
||||||
mDiffer.submitList(list)
|
// deep copy at least the top list, because otherwise adapter can go crazy
|
||||||
|
mDiffer.submitList(list?.let { CopyOnWriteArrayList(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
|
|
|
@ -388,7 +388,7 @@ class HomeParentItemAdapterPreview(
|
||||||
viewModel.loadMoreHomeScrollResponses()
|
viewModel.loadMoreHomeScrollResponses()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val item = previewAdapter.getItem(position) ?: return
|
val item = previewAdapter.getItemOrNull(position) ?: return
|
||||||
onSelect(item, position)
|
onSelect(item, position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -516,6 +516,7 @@ class HomeParentItemAdapterPreview(
|
||||||
when (preview) {
|
when (preview) {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
previewAdapter.submitList(preview.value.second)
|
previewAdapter.submitList(preview.value.second)
|
||||||
|
previewAdapter.hasMoreItems = preview.value.first
|
||||||
/*if (!.setItems(
|
/*if (!.setItems(
|
||||||
preview.value.second,
|
preview.value.second,
|
||||||
preview.value.first
|
preview.value.first
|
||||||
|
|
|
@ -8,7 +8,7 @@ import androidx.fragment.app.Fragment
|
||||||
import com.lagradost.cloudstream3.LoadResponse
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding
|
import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding
|
||||||
import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding
|
import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding
|
||||||
import com.lagradost.cloudstream3.ui.BaseAdapter
|
import com.lagradost.cloudstream3.ui.NoStateAdapter
|
||||||
import com.lagradost.cloudstream3.ui.ViewHolderState
|
import com.lagradost.cloudstream3.ui.ViewHolderState
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
@ -17,7 +17,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
|
||||||
class HomeScrollAdapter(
|
class HomeScrollAdapter(
|
||||||
fragment: Fragment
|
fragment: Fragment
|
||||||
) : BaseAdapter<LoadResponse, Any>(fragment, "HomeScrollAdapter".hashCode()) {
|
) : NoStateAdapter<LoadResponse>(fragment) {
|
||||||
var hasMoreItems: Boolean = false
|
var hasMoreItems: Boolean = false
|
||||||
|
|
||||||
override fun onCreateContent(parent: ViewGroup): ViewHolderState<Any> {
|
override fun onCreateContent(parent: ViewGroup): ViewHolderState<Any> {
|
||||||
|
|
|
@ -53,6 +53,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
class HomeViewModel : ViewModel() {
|
class HomeViewModel : ViewModel() {
|
||||||
|
@ -125,7 +126,7 @@ class HomeViewModel : ViewModel() {
|
||||||
|
|
||||||
private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
|
private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
|
||||||
private val _preview = MutableLiveData<Resource<Pair<Boolean, List<LoadResponse>>>>()
|
private val _preview = MutableLiveData<Resource<Pair<Boolean, List<LoadResponse>>>>()
|
||||||
private val previewResponses = mutableListOf<LoadResponse>()
|
private val previewResponses = CopyOnWriteArrayList<LoadResponse>()
|
||||||
private val previewResponsesAdded = mutableSetOf<String>()
|
private val previewResponsesAdded = mutableSetOf<String>()
|
||||||
|
|
||||||
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
|
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
|
||||||
|
@ -327,7 +328,13 @@ class HomeViewModel : ViewModel() {
|
||||||
val filteredList =
|
val filteredList =
|
||||||
context?.filterHomePageListByFilmQuality(list) ?: list
|
context?.filterHomePageListByFilmQuality(list) ?: list
|
||||||
expandable[list.name] =
|
expandable[list.name] =
|
||||||
ExpandableHomepageList(filteredList, 1, home.hasNext)
|
ExpandableHomepageList(
|
||||||
|
filteredList.copy(
|
||||||
|
list = CopyOnWriteArrayList(
|
||||||
|
filteredList.list
|
||||||
|
)
|
||||||
|
), 1, home.hasNext
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,8 +349,7 @@ class HomeViewModel : ViewModel() {
|
||||||
val currentList =
|
val currentList =
|
||||||
items.shuffled().filter { it.list.isNotEmpty() }
|
items.shuffled().filter { it.list.isNotEmpty() }
|
||||||
.flatMap { it.list }
|
.flatMap { it.list }
|
||||||
.distinctBy { it.url }
|
.distinctBy { it.url }.toList()
|
||||||
.toList()
|
|
||||||
|
|
||||||
if (currentList.isNotEmpty()) {
|
if (currentList.isNotEmpty()) {
|
||||||
val randomItems =
|
val randomItems =
|
||||||
|
|
|
@ -49,12 +49,10 @@ import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals
|
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
|
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.reduceDragSensitivity
|
import com.lagradost.cloudstream3.utils.AppUtils.reduceDragSensitivity
|
||||||
|
@ -62,6 +60,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
const val LIBRARY_FOLDER = "library_folder"
|
const val LIBRARY_FOLDER = "library_folder"
|
||||||
|
@ -165,7 +164,8 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the color for the search exit icon to the correct theme text color
|
// Set the color for the search exit icon to the correct theme text color
|
||||||
val searchExitIcon = binding?.mainSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
|
val searchExitIcon =
|
||||||
|
binding?.mainSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
|
||||||
val searchExitIconColor = TypedValue()
|
val searchExitIconColor = TypedValue()
|
||||||
|
|
||||||
activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true)
|
activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true)
|
||||||
|
@ -233,7 +233,7 @@ class LibraryFragment : Fragment() {
|
||||||
if (listLibraryItems.isNotEmpty()) {
|
if (listLibraryItems.isNotEmpty()) {
|
||||||
val listLibraryItem = listLibraryItems.random()
|
val listLibraryItem = listLibraryItems.random()
|
||||||
libraryViewModel.currentSyncApi?.syncIdName?.let {
|
libraryViewModel.currentSyncApi?.syncIdName?.let {
|
||||||
loadLibraryItem(it, listLibraryItem.syncId,listLibraryItem)
|
loadLibraryItem(it, listLibraryItem.syncId, listLibraryItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -312,44 +312,46 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
binding?.viewpager?.setPageTransformer(LibraryScrollTransformer())
|
binding?.viewpager?.setPageTransformer(LibraryScrollTransformer())
|
||||||
|
|
||||||
binding?.viewpager?.adapter =
|
binding?.viewpager?.adapter = ViewpagerAdapter(
|
||||||
binding?.viewpager?.adapter ?: ViewpagerAdapter(
|
fragment = this,
|
||||||
mutableListOf(),
|
{ isScrollingDown: Boolean ->
|
||||||
{ isScrollingDown: Boolean ->
|
if (isScrollingDown) {
|
||||||
if (isScrollingDown) {
|
binding?.sortFab?.shrink()
|
||||||
binding?.sortFab?.shrink()
|
binding?.libraryRandom?.shrink()
|
||||||
binding?.libraryRandom?.shrink()
|
} else {
|
||||||
} else {
|
binding?.sortFab?.extend()
|
||||||
binding?.sortFab?.extend()
|
binding?.libraryRandom?.extend()
|
||||||
binding?.libraryRandom?.extend()
|
}
|
||||||
}
|
}) callback@{ searchClickCallback ->
|
||||||
}) callback@{ searchClickCallback ->
|
// To prevent future accidents
|
||||||
// To prevent future accidents
|
debugAssert({
|
||||||
debugAssert({
|
searchClickCallback.card !is SyncAPI.LibraryItem
|
||||||
searchClickCallback.card !is SyncAPI.LibraryItem
|
}, {
|
||||||
}, {
|
"searchClickCallback ${searchClickCallback.card} is not a LibraryItem"
|
||||||
"searchClickCallback ${searchClickCallback.card} is not a LibraryItem"
|
})
|
||||||
})
|
|
||||||
|
|
||||||
val syncId = (searchClickCallback.card as SyncAPI.LibraryItem).syncId
|
val syncId = (searchClickCallback.card as SyncAPI.LibraryItem).syncId
|
||||||
val syncName =
|
val syncName =
|
||||||
libraryViewModel.currentSyncApi?.syncIdName ?: return@callback
|
libraryViewModel.currentSyncApi?.syncIdName ?: return@callback
|
||||||
|
|
||||||
when (searchClickCallback.action) {
|
when (searchClickCallback.action) {
|
||||||
SEARCH_ACTION_SHOW_METADATA -> {
|
SEARCH_ACTION_SHOW_METADATA -> {
|
||||||
(activity as? MainActivity)?.loadPopup(searchClickCallback.card, load = false)
|
(activity as? MainActivity)?.loadPopup(
|
||||||
|
searchClickCallback.card,
|
||||||
|
load = false
|
||||||
|
)
|
||||||
/*activity?.showPluginSelectionDialog(
|
/*activity?.showPluginSelectionDialog(
|
||||||
syncId,
|
syncId,
|
||||||
syncName,
|
syncName,
|
||||||
searchClickCallback.card.apiName
|
searchClickCallback.card.apiName
|
||||||
)*/
|
)*/
|
||||||
}
|
}
|
||||||
|
|
||||||
SEARCH_ACTION_LOAD -> {
|
SEARCH_ACTION_LOAD -> {
|
||||||
loadLibraryItem(syncName, syncId, searchClickCallback.card)
|
loadLibraryItem(syncName, syncId, searchClickCallback.card)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding?.apply {
|
binding?.apply {
|
||||||
viewpager.offscreenPageLimit = 2
|
viewpager.offscreenPageLimit = 2
|
||||||
|
@ -395,7 +397,11 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(viewpager.adapter as? ViewpagerAdapter)?.pages = pages
|
(viewpager.adapter as? ViewpagerAdapter)?.submitList(pages.map {
|
||||||
|
it.copy(
|
||||||
|
items = CopyOnWriteArrayList(it.items)
|
||||||
|
)
|
||||||
|
})
|
||||||
//fix focus on the viewpager itself
|
//fix focus on the viewpager itself
|
||||||
(viewpager.getChildAt(0) as RecyclerView).apply {
|
(viewpager.getChildAt(0) as RecyclerView).apply {
|
||||||
tag = "tv_no_focus_tag"
|
tag = "tv_no_focus_tag"
|
||||||
|
@ -403,10 +409,10 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using notifyItemRangeChanged keeps the animations when sorting
|
// Using notifyItemRangeChanged keeps the animations when sorting
|
||||||
viewpager.adapter?.notifyItemRangeChanged(
|
/*viewpager.adapter?.notifyItemRangeChanged(
|
||||||
0,
|
0,
|
||||||
viewpager.adapter?.itemCount ?: 0
|
viewpager.adapter?.itemCount ?: 0
|
||||||
)
|
)*/
|
||||||
|
|
||||||
libraryViewModel.currentPage.value?.let { page ->
|
libraryViewModel.currentPage.value?.let { page ->
|
||||||
binding?.viewpager?.setCurrentItem(page, false)
|
binding?.viewpager?.setCurrentItem(page, false)
|
||||||
|
@ -464,12 +470,14 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
binding?.libraryTabLayout?.addOnTabSelectedListener(object: TabLayout.OnTabSelectedListener {
|
binding?.libraryTabLayout?.addOnTabSelectedListener(object :
|
||||||
|
TabLayout.OnTabSelectedListener {
|
||||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||||
binding?.libraryTabLayout?.selectedTabPosition?.let { page ->
|
binding?.libraryTabLayout?.selectedTabPosition?.let { page ->
|
||||||
libraryViewModel.switchPage(page)
|
libraryViewModel.switchPage(page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
|
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
|
||||||
override fun onTabReselected(tab: TabLayout.Tab?) = Unit
|
override fun onTabReselected(tab: TabLayout.Tab?) = Unit
|
||||||
})
|
})
|
||||||
|
@ -569,8 +577,9 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
(binding?.viewpager?.adapter as? ViewpagerAdapter)?.rebind()
|
binding?.viewpager?.adapter?.notifyDataSetChanged()
|
||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,7 @@ class LibraryViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val desiredSortingMethod =
|
val desiredSortingMethod =
|
||||||
ListSorting.values().getOrNull(DataStoreHelper.librarySortingMode)
|
ListSorting.entries.getOrNull(DataStoreHelper.librarySortingMode)
|
||||||
if (desiredSortingMethod != null && library.supportedListSorting.contains(desiredSortingMethod)) {
|
if (desiredSortingMethod != null && library.supportedListSorting.contains(desiredSortingMethod)) {
|
||||||
sort(desiredSortingMethod, null, pages)
|
sort(desiredSortingMethod, null, pages)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,105 +1,123 @@
|
||||||
package com.lagradost.cloudstream3.ui.library
|
package com.lagradost.cloudstream3.ui.library
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.doOnAttach
|
import androidx.core.view.doOnAttach
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.RecyclerView.OnFlingListener
|
import androidx.recyclerview.widget.RecyclerView.OnFlingListener
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding
|
import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
|
import com.lagradost.cloudstream3.ui.BaseAdapter
|
||||||
|
import com.lagradost.cloudstream3.ui.BaseDiffCallback
|
||||||
|
import com.lagradost.cloudstream3.ui.ViewHolderState
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
|
|
||||||
|
class ViewpagerAdapterViewHolderState(val binding: LibraryViewpagerPageBinding) :
|
||||||
|
ViewHolderState<Bundle>(binding) {
|
||||||
|
override fun save(): Bundle =
|
||||||
|
Bundle().apply {
|
||||||
|
putParcelable(
|
||||||
|
"pageRecyclerview",
|
||||||
|
binding.pageRecyclerview.layoutManager?.onSaveInstanceState()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun restore(state: Bundle) {
|
||||||
|
state.getParcelable<Parcelable>("pageRecyclerview")?.let { recycle ->
|
||||||
|
binding.pageRecyclerview.layoutManager?.onRestoreInstanceState(recycle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ViewpagerAdapter(
|
class ViewpagerAdapter(
|
||||||
var pages: List<SyncAPI.Page>,
|
fragment: Fragment,
|
||||||
val scrollCallback: (isScrollingDown: Boolean) -> Unit,
|
val scrollCallback: (isScrollingDown: Boolean) -> Unit,
|
||||||
val clickCallback: (SearchClickCallback) -> Unit
|
val clickCallback: (SearchClickCallback) -> Unit
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : BaseAdapter<SyncAPI.Page, Bundle>(fragment,
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
id = "ViewpagerAdapter".hashCode(),
|
||||||
return PageViewHolder(
|
diffCallback = BaseDiffCallback(
|
||||||
|
itemSame = { a, b ->
|
||||||
|
a.title == b.title
|
||||||
|
},
|
||||||
|
contentSame = { a, b ->
|
||||||
|
a.items == b.items && a.title == b.title
|
||||||
|
}
|
||||||
|
)) {
|
||||||
|
override fun onCreateContent(parent: ViewGroup): ViewHolderState<Bundle> {
|
||||||
|
return ViewpagerAdapterViewHolderState(
|
||||||
LibraryViewpagerPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
LibraryViewpagerPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onUpdateContent(
|
||||||
when (holder) {
|
holder: ViewHolderState<Bundle>,
|
||||||
is PageViewHolder -> {
|
item: SyncAPI.Page,
|
||||||
holder.bind(pages[position], position, unbound.remove(position))
|
position: Int
|
||||||
}
|
) {
|
||||||
}
|
val binding = holder.view
|
||||||
|
if (binding !is LibraryViewpagerPageBinding) return
|
||||||
|
(binding.pageRecyclerview.adapter as? PageAdapter)?.updateList(item.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val unbound = mutableSetOf<Int>()
|
override fun onBindContent(holder: ViewHolderState<Bundle>, item: SyncAPI.Page, position: Int) {
|
||||||
|
val binding = holder.view
|
||||||
|
if (binding !is LibraryViewpagerPageBinding) return
|
||||||
|
|
||||||
/**
|
binding.pageRecyclerview.tag = position
|
||||||
* Used to mark all pages for re-binding and forces all items to be refreshed
|
binding.pageRecyclerview.apply {
|
||||||
* Without this the pages will still use the same adapters
|
spanCount =
|
||||||
**/
|
binding.root.context.getSpanCount() ?: 3
|
||||||
fun rebind() {
|
if (adapter == null) { // || rebind
|
||||||
unbound.addAll(0..pages.size)
|
// Only add the items after it has been attached since the items rely on ItemWidth
|
||||||
this.notifyItemRangeChanged(0, pages.size)
|
// Which is only determined after the recyclerview is attached.
|
||||||
}
|
// If this fails then item height becomes 0 when there is only one item
|
||||||
|
doOnAttach {
|
||||||
inner class PageViewHolder(private val binding: LibraryViewpagerPageBinding) :
|
adapter = PageAdapter(
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
item.items.toMutableList(),
|
||||||
fun bind(page: SyncAPI.Page, position: Int, rebind: Boolean) {
|
this,
|
||||||
binding.pageRecyclerview.tag = position
|
clickCallback
|
||||||
binding.pageRecyclerview.apply {
|
)
|
||||||
spanCount =
|
|
||||||
this@PageViewHolder.itemView.context.getSpanCount() ?: 3
|
|
||||||
if (adapter == null || rebind) {
|
|
||||||
// Only add the items after it has been attached since the items rely on ItemWidth
|
|
||||||
// Which is only determined after the recyclerview is attached.
|
|
||||||
// If this fails then item height becomes 0 when there is only one item
|
|
||||||
doOnAttach {
|
|
||||||
adapter = PageAdapter(
|
|
||||||
page.items.toMutableList(),
|
|
||||||
this,
|
|
||||||
clickCallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(adapter as? PageAdapter)?.updateList(page.items)
|
|
||||||
scrollToPosition(0)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
(adapter as? PageAdapter)?.updateList(item.items)
|
||||||
|
// scrollToPosition(0)
|
||||||
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
val diff = scrollY - oldScrollY
|
val diff = scrollY - oldScrollY
|
||||||
|
|
||||||
//Expand the top Appbar based on scroll direction up/down, simulate phone behavior
|
//Expand the top Appbar based on scroll direction up/down, simulate phone behavior
|
||||||
if (isLayout(TV or EMULATOR)) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
binding.root.rootView.findViewById<AppBarLayout>(R.id.search_bar)
|
binding.root.rootView.findViewById<AppBarLayout>(R.id.search_bar)
|
||||||
.apply {
|
.apply {
|
||||||
if (diff <= 0)
|
if (diff <= 0)
|
||||||
setExpanded(true)
|
setExpanded(true)
|
||||||
else
|
else
|
||||||
setExpanded(false)
|
setExpanded(false)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (diff == 0) return@setOnScrollChangeListener
|
|
||||||
|
|
||||||
scrollCallback.invoke(diff > 0)
|
|
||||||
}
|
}
|
||||||
} else {
|
if (diff == 0) return@setOnScrollChangeListener
|
||||||
onFlingListener = object : OnFlingListener() {
|
|
||||||
override fun onFling(velocityX: Int, velocityY: Int): Boolean {
|
scrollCallback.invoke(diff > 0)
|
||||||
scrollCallback.invoke(velocityY > 0)
|
}
|
||||||
return false
|
} else {
|
||||||
}
|
onFlingListener = object : OnFlingListener() {
|
||||||
|
override fun onFling(velocityX: Int, velocityY: Int): Boolean {
|
||||||
|
scrollCallback.invoke(velocityY > 0)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return pages.size
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -19,6 +19,13 @@ sealed class UiText {
|
||||||
|
|
||||||
data class DynamicString(val value: String) : UiText() {
|
data class DynamicString(val value: String) : UiText() {
|
||||||
override fun toString(): String = value
|
override fun toString(): String = value
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is DynamicString) return false
|
||||||
|
return this.value == other.value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = value.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
class StringResource(
|
class StringResource(
|
||||||
|
@ -27,6 +34,16 @@ sealed class UiText {
|
||||||
) : UiText() {
|
) : UiText() {
|
||||||
override fun toString(): String =
|
override fun toString(): String =
|
||||||
"resId = $resId\nargs = ${args.toList().map { "(${it::class} = $it)" }}"
|
"resId = $resId\nargs = ${args.toList().map { "(${it::class} = $it)" }}"
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is StringResource) return false
|
||||||
|
return this.resId == other.resId && this.args == other.args
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = resId
|
||||||
|
result = 31 * result + args.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun asStringNull(context: Context?): String? {
|
fun asStringNull(context: Context?): String? {
|
||||||
|
|
Loading…
Reference in a new issue