From 8d5b73495d6b5bb1d5ba2e3155a25bf59a60d5f0 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Mon, 18 Mar 2024 03:58:30 +0100 Subject: [PATCH] Added BaseAdapter to store internal state --- .../lagradost/cloudstream3/ui/BaseAdapter.kt | 246 +++++++++++++ .../ui/NonFinalAdapterListUpdateCallback.kt | 39 ++ .../ui/home/HomeChildItemAdapter.kt | 263 ++++++------- .../cloudstream3/ui/home/HomeFragment.kt | 21 +- .../ui/home/HomeParentItemAdapter.kt | 344 +++++------------- .../ui/home/HomeParentItemAdapterPreview.kt | 298 +++++++-------- .../cloudstream3/ui/home/HomeScrollAdapter.kt | 123 ++----- .../ui/player/FullScreenPlayer.kt | 6 +- .../ui/quicksearch/QuickSearchFragment.kt | 2 +- .../cloudstream3/ui/search/SearchFragment.kt | 8 +- 10 files changed, 671 insertions(+), 679 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/NonFinalAdapterListUpdateCallback.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt new file mode 100644 index 00000000..825087c5 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt @@ -0,0 +1,246 @@ +package com.lagradost.cloudstream3.ui + +import android.view.View +import android.view.ViewGroup +import androidx.core.view.children +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.ViewModel +import androidx.recyclerview.widget.AsyncDifferConfig +import androidx.recyclerview.widget.AsyncListDiffer +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import androidx.viewbinding.ViewBinding + +open class ViewHolderState(val view: ViewBinding) : ViewHolder(view.root) { + open fun save(): T? = null + open fun restore(state: T) = Unit + open fun onViewAttachedToWindow() = Unit + open fun onViewDetachedFromWindow() = Unit + open fun onViewRecycled() = Unit +} + + +// Based of the concept https://github.com/brahmkshatriya/echo/blob/main/app%2Fsrc%2Fmain%2Fjava%2Fdev%2Fbrahmkshatriya%2Fecho%2Fui%2Fadapters%2FMediaItemsContainerAdapter.kt#L108-L154 +class StateViewModel : ViewModel() { + val layoutManagerStates = hashMapOf>() +} + +/** + * 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. + * + * Id is a per fragment based unique id used to store the underlying data done in an internal ViewModel. + * + * diffCallback is how the view should be handled when updating, override onUpdateContent for updates + * + * NOTE: + * + * By default it should save automatically, but you can also call save(recycle) + * + * By default no state is stored, but doing an id != 0 will store + * + * By default no headers or footers exist, override footers and headers count + */ +abstract class BaseAdapter< + T : Any, + S : Any>( + fragment: Fragment, + val id: Int = 0, + diffCallback: DiffUtil.ItemCallback = BaseDiffCallback() +) : RecyclerView.Adapter>() { + open val footers: Int = 0 + open val headers: Int = 0 + + fun getItem(position: Int): T { + return mDiffer.currentList[position] + } + + fun getItemOrNull(position: Int): T? { + return mDiffer.currentList.getOrNull(position) + } + + private val mDiffer: AsyncListDiffer = AsyncListDiffer( + object : NonFinalAdapterListUpdateCallback(this) { + override fun onMoved(fromPosition: Int, toPosition: Int) { + super.onMoved(fromPosition + headers, toPosition + headers) + } + + override fun onRemoved(position: Int, count: Int) { + super.onRemoved(position + headers, count) + } + + override fun onChanged(position: Int, count: Int, payload: Any?) { + super.onChanged(position + headers, count, payload) + } + + override fun onInserted(position: Int, count: Int) { + super.onInserted(position + headers, count) + } + }, + AsyncDifferConfig.Builder(diffCallback).build() + ) + + fun submitList(list: List?) { + mDiffer.submitList(list) + } + + override fun getItemCount(): Int { + return mDiffer.currentList.size + footers + headers + } + + open fun onUpdateContent(holder: ViewHolderState, item: T, position: Int) = + onBindContent(holder, item, position) + + open fun onBindContent(holder: ViewHolderState, item: T, position: Int) = Unit + open fun onBindFooter(holder: ViewHolderState) = Unit + open fun onBindHeader(holder: ViewHolderState) = Unit + open fun onCreateContent(parent: ViewGroup): ViewHolderState = throw NotImplementedError() + open fun onCreateFooter(parent: ViewGroup): ViewHolderState = throw NotImplementedError() + open fun onCreateHeader(parent: ViewGroup): ViewHolderState = throw NotImplementedError() + + override fun onViewAttachedToWindow(holder: ViewHolderState) { + holder.onViewAttachedToWindow() + } + + override fun onViewDetachedFromWindow(holder: ViewHolderState) { + holder.onViewDetachedFromWindow() + } + + fun save(recyclerView: RecyclerView) { + for (child in recyclerView.children) { + val holder = + recyclerView.findContainingViewHolder(child) as? ViewHolderState ?: continue + setState(holder) + } + } + + fun clear() { + stateViewModel.layoutManagerStates[id]?.clear() + } + + private fun getState(holder: ViewHolderState): S? = + stateViewModel.layoutManagerStates[id]?.get(holder.absoluteAdapterPosition) as? S + + private fun setState(holder: ViewHolderState) { + if(id == 0) return + + if (!stateViewModel.layoutManagerStates.contains(id)) { + stateViewModel.layoutManagerStates[id] = HashMap() + } + stateViewModel.layoutManagerStates[id]?.let { map -> + map[holder.absoluteAdapterPosition] = holder.save() + } + } + + private val attachListener = object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) = Unit + override fun onViewDetachedFromWindow(v: View) { + if (v !is RecyclerView) return + save(v) + } + } + + final override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + recyclerView.addOnAttachStateChangeListener(attachListener) + super.onAttachedToRecyclerView(recyclerView) + } + + final override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + recyclerView.removeOnAttachStateChangeListener(attachListener) + super.onDetachedFromRecyclerView(recyclerView) + } + + final override fun getItemViewType(position: Int): Int { + if (position < headers) { + return HEADER + } + if (position - headers >= mDiffer.currentList.size) { + return FOOTER + } + + return CONTENT + } + + private val stateViewModel: StateViewModel by fragment.viewModels() + + final override fun onViewRecycled(holder: ViewHolderState) { + setState(holder) + holder.onViewRecycled() + super.onViewRecycled(holder) + } + + final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderState { + return when (viewType) { + CONTENT -> onCreateContent(parent) + HEADER -> onCreateHeader(parent) + FOOTER -> onCreateFooter(parent) + else -> throw NotImplementedError() + } + } + + // https://medium.com/@domen.lanisnik/efficiently-updating-recyclerview-items-using-payloads-1305f65f3068 + override fun onBindViewHolder( + holder: ViewHolderState, + position: Int, + payloads: MutableList + ) { + if (payloads.isEmpty()) { + super.onBindViewHolder(holder, position, payloads) + return + } + when (getItemViewType(position)) { + CONTENT -> { + val realPosition = position - headers + val item = getItem(realPosition) + onUpdateContent(holder, item, realPosition) + } + + FOOTER -> { + onBindFooter(holder) + } + + HEADER -> { + onBindHeader(holder) + } + } + } + + final override fun onBindViewHolder(holder: ViewHolderState, position: Int) { + when (getItemViewType(position)) { + CONTENT -> { + val realPosition = position - headers + val item = getItem(realPosition) + onBindContent(holder, item, realPosition) + } + + FOOTER -> { + onBindFooter(holder) + } + + HEADER -> { + onBindHeader(holder) + } + } + + getState(holder)?.let { state -> + holder.restore(state) + } + } + + companion object { + private const val HEADER: Int = 1 + private const val FOOTER: Int = 2 + private const val CONTENT: Int = 0 + } +} + +class BaseDiffCallback( + val itemSame: (T, T) -> Boolean = { a, b -> a.hashCode() == b.hashCode() }, + val contentSame: (T, T) -> Boolean = { a, b -> a.hashCode() == b.hashCode() } +) : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = itemSame(oldItem, newItem) + override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = contentSame(oldItem, newItem) + override fun getChangePayload(oldItem: T, newItem: T): Any = Any() +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/NonFinalAdapterListUpdateCallback.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/NonFinalAdapterListUpdateCallback.kt new file mode 100644 index 00000000..f721401e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/NonFinalAdapterListUpdateCallback.kt @@ -0,0 +1,39 @@ +package com.lagradost.cloudstream3.ui + +import android.annotation.SuppressLint +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListUpdateCallback +import androidx.recyclerview.widget.RecyclerView + + +/** + * ListUpdateCallback that dispatches update events to the given adapter. + * + * @see DiffUtil.DiffResult.dispatchUpdatesTo + */ +open class NonFinalAdapterListUpdateCallback +/** + * Creates an AdapterListUpdateCallback that will dispatch update events to the given adapter. + * + * @param adapter The Adapter to send updates to. + */(private var mAdapter: RecyclerView.Adapter<*>) : + ListUpdateCallback { + + override fun onInserted(position: Int, count: Int) { + mAdapter.notifyItemRangeInserted(position, count) + } + + override fun onRemoved(position: Int, count: Int) { + mAdapter.notifyItemRangeRemoved(position, count) + } + + override fun onMoved(fromPosition: Int, toPosition: Int) { + mAdapter.notifyItemMoved(fromPosition, toPosition) + } + + @SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly + override fun onChanged(position: Int, count: Int, payload: Any?) { + mAdapter.notifyItemRangeChanged(position, count, payload) + } +} + diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt index f84966eb..ebed901f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt @@ -2,31 +2,58 @@ package com.lagradost.cloudstream3.ui.home import android.view.LayoutInflater import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView +import androidx.fragment.app.Fragment import androidx.viewbinding.ViewBinding import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.databinding.HomeResultGridBinding import com.lagradost.cloudstream3.databinding.HomeResultGridExpandedBinding +import com.lagradost.cloudstream3.ui.BaseAdapter +import com.lagradost.cloudstream3.ui.ViewHolderState +import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchResultBuilder -import com.lagradost.cloudstream3.utils.AppUtils.isRtl +import com.lagradost.cloudstream3.ui.settings.Globals.TV +import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout import com.lagradost.cloudstream3.utils.UIHelper.toPx -class HomeChildItemAdapter( - val cardList: MutableList, +class HomeScrollViewHolderState(view: ViewBinding) : ViewHolderState(view) { + /*private fun recursive(view : View) : Boolean { + if (view.isFocused) { + println("VIEW: $view | id=${view.id}") + } + return (view as? ViewGroup)?.children?.any { recursive(it) } ?: false + }*/ + // very shitty that we cant store the state when the view clears, + // but this is because the focus clears before the view is removed + // so we have to manually store it + var wasFocused: Boolean = false + override fun save(): Boolean = wasFocused + override fun restore(state: Boolean) { + if (state) { + wasFocused = false + // only refocus if tv + if(isLayout(TV)) { + itemView.requestFocus() + } + } + } +} + +class HomeChildItemAdapter( + fragment: Fragment, + id: Int, private val nextFocusUp: Int? = null, private val nextFocusDown: Int? = null, private val clickCallback: (SearchClickCallback) -> Unit, ) : - RecyclerView.Adapter() { + BaseAdapter(fragment, id) { var isHorizontal: Boolean = false var hasNext: Boolean = false - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + override fun onCreateContent(parent: ViewGroup): ViewHolderState { val expanded = parent.context.IsBottomLayout() /* val layout = if (bottom) R.layout.home_result_grid_expanded else R.layout.home_result_grid @@ -39,164 +66,78 @@ class HomeChildItemAdapter( parent, false ) else HomeResultGridBinding.inflate(inflater, parent, false) + return HomeScrollViewHolderState(binding) + } + override fun onBindContent( + holder: ViewHolderState, + item: SearchResponse, + position: Int + ) { + when (val binding = holder.view) { + is HomeResultGridBinding -> { + binding.backgroundCard.apply { + val min = 114.toPx + val max = 180.toPx - return CardViewHolder( - binding, - clickCallback, - itemCount, + layoutParams = + layoutParams.apply { + width = if (!isHorizontal) { + min + } else { + max + } + height = if (!isHorizontal) { + max + } else { + min + } + } + } + } + + is HomeResultGridExpandedBinding -> { + binding.backgroundCard.apply { + val min = 114.toPx + val max = 180.toPx + + layoutParams = + layoutParams.apply { + width = if (!isHorizontal) { + min + } else { + max + } + height = if (!isHorizontal) { + max + } else { + min + } + } + } + + if (position == 0) { // to fix tv + binding.backgroundCard.nextFocusLeftId = R.id.nav_rail_view + } + } + } + + SearchResultBuilder.bind( + clickCallback = { click -> + // ok, so here we hijack the callback to fix the focus + when (click.action) { + SEARCH_ACTION_LOAD -> (holder as? HomeScrollViewHolderState)?.wasFocused = true + } + clickCallback(click) + }, + item, + position, + holder.itemView, + null, // nextFocusBehavior, nextFocusUp, - nextFocusDown, - isHorizontal, - parent.isRtl() - ) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder) { - is CardViewHolder -> { - holder.itemCount = itemCount // i know ugly af - holder.bind(cardList[position], position) - } - } - } - - override fun getItemCount(): Int { - return cardList.size - } - - override fun getItemId(position: Int): Long { - return (cardList[position].id ?: position).toLong() - } - - fun updateList(newList: List) { - val diffResult = DiffUtil.calculateDiff( - HomeChildDiffCallback(this.cardList, newList) + nextFocusDown ) - cardList.clear() - cardList.addAll(newList) - - diffResult.dispatchUpdatesTo(this) - } - - class CardViewHolder - constructor( - val binding: ViewBinding, - private val clickCallback: (SearchClickCallback) -> Unit, - var itemCount: Int, - private val nextFocusUp: Int? = null, - private val nextFocusDown: Int? = null, - private val isHorizontal: Boolean = false, - private val isRtl: Boolean - ) : - RecyclerView.ViewHolder(binding.root) { - - fun bind(card: SearchResponse, position: Int) { - - // TV focus fixing - /*val nextFocusBehavior = when (position) { - 0 -> true - itemCount - 1 -> false - else -> null - } - - if (position == 0) { // to fix tv - if (isRtl) { - itemView.nextFocusRightId = R.id.nav_rail_view - itemView.nextFocusLeftId = -1 - } - else { - itemView.nextFocusLeftId = R.id.nav_rail_view - itemView.nextFocusRightId = -1 - } - } else { - itemView.nextFocusRightId = -1 - itemView.nextFocusLeftId = -1 - }*/ - - - when (binding) { - is HomeResultGridBinding -> { - binding.backgroundCard.apply { - val min = 114.toPx - val max = 180.toPx - - layoutParams = - layoutParams.apply { - width = if (!isHorizontal) { - min - } else { - max - } - height = if (!isHorizontal) { - max - } else { - min - } - } - } - - - } - - is HomeResultGridExpandedBinding -> { - binding.backgroundCard.apply { - val min = 114.toPx - val max = 180.toPx - - layoutParams = - layoutParams.apply { - width = if (!isHorizontal) { - min - } else { - max - } - height = if (!isHorizontal) { - max - } else { - min - } - } - } - - if (position == 0) { // to fix tv - binding.backgroundCard.nextFocusLeftId = R.id.nav_rail_view - } - } - } - - SearchResultBuilder.bind( - clickCallback, - card, - position, - itemView, - null, // nextFocusBehavior, - nextFocusUp, - nextFocusDown - ) - itemView.tag = position - - //val ani = ScaleAnimation(0.9f, 1.0f, 0.9f, 1f) - //ani.fillAfter = true - //ani.duration = 200 - //itemView.startAnimation(ani) - } + holder.itemView.tag = position } } - -class HomeChildDiffCallback( - private val oldList: List, - private val newList: List -) : - DiffUtil.Callback() { - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = - oldList[oldItemPosition].name == newList[newItemPosition].name - - override fun getOldListSize() = oldList.size - - override fun getNewListSize() = newList.size - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = - oldList[oldItemPosition] == newList[newItemPosition] && oldItemPosition < oldList.size - 1 // always update the last item -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 7a68330f..12185cbf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -451,10 +451,6 @@ class HomeFragment : Fragment() { } override fun onDestroyView() { - homeMasterAdapter?.onSaveInstanceState( - instanceState, - binding?.homeMasterRecycler - ) bottomSheetDialog?.ownHide() binding = null @@ -517,11 +513,9 @@ class HomeFragment : Fragment() { } } homeMasterAdapter = HomeParentItemAdapterPreview( - mutableListOf(), + fragment = this@HomeFragment, homeViewModel, - ).apply { - onRestoreInstanceState(instanceState) - } + ) homeMasterRecycler.adapter = homeMasterAdapter //fixPaddingStatusbar(homeLoadingStatusbar) @@ -572,10 +566,11 @@ class HomeFragment : Fragment() { val mutableListOfResponse = mutableListOf() listHomepageItems.clear() - (homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList( - d.values.toMutableList(), - homeMasterRecycler - ) + (homeMasterRecycler.adapter as? ParentItemAdapter)?.submitList(d.values.map { + it.copy( + list = it.list.copy(list = it.list.list.toMutableList()) + ) + }.toMutableList()) homeLoading.isVisible = false homeLoadingError.isVisible = false @@ -624,7 +619,7 @@ class HomeFragment : Fragment() { } is Resource.Loading -> { - (homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList(listOf()) + (homeMasterRecycler.adapter as? ParentItemAdapter)?.submitList(listOf()) homeLoadingShimmer.startShimmer() homeLoading.isVisible = true homeLoadingError.isVisible = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 03ec1a6c..fb75e772 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -1,24 +1,23 @@ package com.lagradost.cloudstream3.ui.home import android.os.Bundle -import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.TextView -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListUpdateCallback +import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.ViewHolder +import androidx.viewbinding.ViewBinding import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.HomepageParentBinding import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.BaseAdapter +import com.lagradost.cloudstream3.ui.BaseDiffCallback +import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.search.SearchClickCallback -import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.PHONE import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -33,256 +32,85 @@ class LoadClickCallback( ) open class ParentItemAdapter( - private var items: MutableList, - //private val viewModel: HomeViewModel, + open val fragment: Fragment, + id: Int, private val clickCallback: (SearchClickCallback) -> Unit, private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit, private val expandCallback: ((String) -> Unit)? = null, -) : RecyclerView.Adapter() { - // Ok, this is fucked, but there is a reason for this as we want to resume 1. when scrolling up and down - // and 2. when doing into a thing and coming back. 1 is always active, but 2 requires doing it in the fragment - // as OnCreateView is called and this adapter is recreated losing the internal state to the GC - // - // 1. This works by having the adapter having a internal state "scrollStates" that keeps track of the states - // when a view recycles, it looks up this internal state - // 2. To solve the the coming back shit we have to save "scrollStates" to a Bundle inside the - // fragment via onSaveInstanceState, because this cant be easy for some reason as the adapter does - // not have a state but the layout-manager for no reason, then it is resumed via onRestoreInstanceState - // - // Even when looking at a real example they do this :skull: - // https://github.com/vivchar/RendererRecyclerViewAdapter/blob/185251ee9d94fb6eb3e063b00d646b745186c365/example/src/main/java/com/github/vivchar/example/pages/github/GithubFragment.kt#L32 - private val scrollStates = mutableMapOf() - - companion object { - private const val SCROLL_KEY: String = "ParentItemAdapter::scrollStates.keys" - private const val SCROLL_VALUE: String = "ParentItemAdapter::scrollStates.values" - } - - open fun onRestoreInstanceState(savedInstanceState: Bundle?) { - try { - val keys = savedInstanceState?.getIntArray(SCROLL_KEY) ?: intArrayOf() - val values = savedInstanceState?.getParcelableArray(SCROLL_VALUE) ?: arrayOf() - for ((k, v) in keys.zip(values)) { - this.scrollStates[k] = v - } - } catch (t: Throwable) { - logError(t) - } - } - - open fun onSaveInstanceState(outState: Bundle, recyclerView: RecyclerView? = null) { - if (recyclerView != null) { - for (position in 0..itemCount) { - val holder = recyclerView.findViewHolderForAdapterPosition(position) ?: continue - saveHolder(holder) - } - } - - outState.putIntArray(SCROLL_KEY, scrollStates.keys.toIntArray()) - outState.putParcelableArray(SCROLL_VALUE, scrollStates.values.toTypedArray()) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - when (holder) { - is ParentViewHolder -> { - holder.bind(items[position]) - scrollStates[holder.absoluteAdapterPosition]?.let { - holder.binding.homeChildRecyclerview.layoutManager?.onRestoreInstanceState(it) - } - } - } - } - - private fun saveHolder(holder : ViewHolder) { - when (holder) { - is ParentViewHolder -> { - scrollStates[holder.absoluteAdapterPosition] = - holder.binding.homeChildRecyclerview.layoutManager?.onSaveInstanceState() - } - } - } - - override fun onViewRecycled(holder: ViewHolder) { - saveHolder(holder) - super.onViewRecycled(holder) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val layoutResId = when { - isLayout(TV) -> R.layout.homepage_parent_tv - isLayout(EMULATOR) -> R.layout.homepage_parent_emulator - else -> R.layout.homepage_parent - } - - val inflater = LayoutInflater.from(parent.context) - val binding = try { - HomepageParentBinding.bind(inflater.inflate(layoutResId, parent, false)) - } catch (t : Throwable) { - logError(t) - // just in case someone forgot we don't want to crash - HomepageParentBinding.inflate(inflater) - } - - return ParentViewHolder( - binding, - clickCallback, - moreInfoClickCallback, - expandCallback - ) - } - - override fun getItemCount(): Int { - return items.size - } - - override fun getItemId(position: Int): Long { - return items[position].list.name.hashCode().toLong() - } - - @JvmName("updateListHomePageList") - fun updateList(newList: List) { - updateList(newList.map { HomeViewModel.ExpandableHomepageList(it, 1, false) } - .toMutableList()) - } - - @JvmName("updateListExpandableHomepageList") - fun updateList( - newList: MutableList, - recyclerView: RecyclerView? = null - ) { - // this - // 1. prevents deep copy that makes this.items == newList - // 2. filters out undesirable results - // 3. moves empty results to the bottom (sortedBy is a stable sort) - val new = - newList.map { it.copy(list = it.list.copy(list = it.list.list.filterSearchResponse())) } - .sortedBy { it.list.list.isEmpty() } - - val diffResult = DiffUtil.calculateDiff( - SearchDiffCallback(items, new) - ) - items.clear() - items.addAll(new) - - //val mAdapter = this - val delta = if (this@ParentItemAdapter is HomeParentItemAdapterPreview) { - headItems - } else { - 0 - } - - diffResult.dispatchUpdatesTo(object : ListUpdateCallback { - override fun onInserted(position: Int, count: Int) { - //notifyItemRangeChanged(position + delta, count) - notifyItemRangeInserted(position + delta, count) - } - - override fun onRemoved(position: Int, count: Int) { - notifyItemRangeRemoved(position + delta, count) - } - - override fun onMoved(fromPosition: Int, toPosition: Int) { - notifyItemMoved(fromPosition + delta, toPosition + delta) - } - - override fun onChanged(_position: Int, count: Int, payload: Any?) { - val position = _position + delta - - // I know kinda messy, what this does is using the update or bind instead of onCreateViewHolder -> bind - recyclerView?.apply { - // this loops every viewHolder in the recycle view and checks the position to see if it is within the update range - val missingUpdates = (position until (position + count)).toMutableSet() - for (i in 0 until itemCount) { - val child = getChildAt(i) ?: continue - val viewHolder = getChildViewHolder(child) ?: continue - if (viewHolder !is ParentViewHolder) continue - - val absolutePosition = viewHolder.bindingAdapterPosition - if (absolutePosition >= position && absolutePosition < position + count) { - val expand = items.getOrNull(absolutePosition - delta) ?: continue - missingUpdates -= absolutePosition - //println("Updating ${viewHolder.title.text} ($absolutePosition $position) -> ${expand.list.name}") - if (viewHolder.title.text == expand.list.name) { - viewHolder.update(expand) - } else { - viewHolder.bind(expand) - } - } - } - - // just in case some item did not get updated - for (i in missingUpdates) { - notifyItemChanged(i, payload) - } - } ?: run { - // in case we don't have a nice - notifyItemRangeChanged(position, count, payload) - } - } +) : BaseAdapter( + fragment, + id, + diffCallback = BaseDiffCallback( + itemSame = { a, b -> a.list.name == b.list.name }, + contentSame = { a, b -> + a.list.list == b.list.list }) - - //diffResult.dispatchUpdatesTo(this) - } - - - class ParentViewHolder( - val binding: HomepageParentBinding, - // val viewModel: HomeViewModel, - private val clickCallback: (SearchClickCallback) -> Unit, - private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit, - private val expandCallback: ((String) -> Unit)? = null, - ) : - ViewHolder(binding.root) { - val title: TextView = binding.homeChildMoreInfo - private val recyclerView: RecyclerView = binding.homeChildRecyclerview - private val startFocus = R.id.nav_rail_view - private val endFocus = FOCUS_SELF - fun update(expand: HomeViewModel.ExpandableHomepageList) { - val info = expand.list - (recyclerView.adapter as? HomeChildItemAdapter?)?.apply { - updateList(info.list.toMutableList()) - hasNext = expand.hasNext - } ?: run { - recyclerView.adapter = HomeChildItemAdapter( - info.list.toMutableList(), - clickCallback = clickCallback, - nextFocusUp = recyclerView.nextFocusUpId, - nextFocusDown = recyclerView.nextFocusDownId, - ).apply { - isHorizontal = info.isHorizontalImages - hasNext = expand.hasNext - } - recyclerView.setLinearListLayout( - isHorizontal = true, - nextLeft = startFocus, - nextRight = endFocus, - ) - } +) { + data class ParentItemHolder(val binding: ViewBinding) : ViewHolderState(binding) { + override fun save(): Bundle = Bundle().apply { + val recyclerView = (binding as? HomepageParentBinding)?.homeChildRecyclerview + putParcelable( + "value", + recyclerView?.layoutManager?.onSaveInstanceState() + ) + (recyclerView?.adapter as? BaseAdapter<*,*>)?.save(recyclerView) } - fun bind(expand: HomeViewModel.ExpandableHomepageList) { - val info = expand.list - recyclerView.adapter = HomeChildItemAdapter( - info.list.toMutableList(), + override fun restore(state: Bundle) { + (binding as? HomepageParentBinding)?.homeChildRecyclerview?.layoutManager?.onRestoreInstanceState( + state.getParcelable("value") + ) + } + } + + override fun onUpdateContent( + holder: ViewHolderState, + item: HomeViewModel.ExpandableHomepageList, + position: Int + ) { + val binding = holder.view + if (binding !is HomepageParentBinding) return + (binding.homeChildRecyclerview.adapter as? HomeChildItemAdapter)?.submitList(item.list.list) + } + + override fun onBindContent( + holder: ViewHolderState, + item: HomeViewModel.ExpandableHomepageList, + position: Int + ) { + val startFocus = R.id.nav_rail_view + val endFocus = FOCUS_SELF + val binding = holder.view + if (binding !is HomepageParentBinding) return + val info = item.list + binding.apply { + homeChildRecyclerview.adapter = HomeChildItemAdapter( + fragment = fragment, + id = id + position + 100, clickCallback = clickCallback, - nextFocusUp = recyclerView.nextFocusUpId, - nextFocusDown = recyclerView.nextFocusDownId, + nextFocusUp = homeChildRecyclerview.nextFocusUpId, + nextFocusDown = homeChildRecyclerview.nextFocusDownId, ).apply { isHorizontal = info.isHorizontalImages - hasNext = expand.hasNext + hasNext = item.hasNext + submitList(item.list.list) } - recyclerView.setLinearListLayout( + homeChildRecyclerview.setLinearListLayout( isHorizontal = true, nextLeft = startFocus, nextRight = endFocus, ) - title.text = info.name + homeChildMoreInfo.text = info.name - recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + homeChildRecyclerview.addOnScrollListener(object : + RecyclerView.OnScrollListener() { var expandCount = 0 - val name = expand.list.name + val name = item.list.name - override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + override fun onScrollStateChanged( + recyclerView: RecyclerView, + newState: Int + ) { super.onScrollStateChanged(recyclerView, newState) val adapter = recyclerView.adapter @@ -307,26 +135,34 @@ open class ParentItemAdapter( //(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged() if (isLayout(PHONE)) { - title.setOnClickListener { - moreInfoClickCallback.invoke(expand) + homeChildMoreInfo.setOnClickListener { + moreInfoClickCallback.invoke(item) } } } } -} -class SearchDiffCallback( - private val oldList: List, - private val newList: List -) : - DiffUtil.Callback() { - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = - oldList[oldItemPosition].list.name == newList[newItemPosition].list.name + override fun onCreateContent(parent: ViewGroup): ParentItemHolder { + val layoutResId = when { + isLayout(TV) -> R.layout.homepage_parent_tv + isLayout(EMULATOR) -> R.layout.homepage_parent_emulator + else -> R.layout.homepage_parent + } - override fun getOldListSize() = oldList.size + val inflater = LayoutInflater.from(parent.context) + val binding = try { + HomepageParentBinding.bind(inflater.inflate(layoutResId, parent, false)) + } catch (t: Throwable) { + logError(t) + // just in case someone forgot we don't want to crash + HomepageParentBinding.inflate(inflater) + } - override fun getNewListSize() = newList.size + return ParentItemHolder(binding) + } - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = - oldList[oldItemPosition] == newList[newItemPosition] -} + fun updateList(newList: List) { + submitList(newList.map { HomeViewModel.ExpandableHomepageList(it, 1, false) } + .toMutableList()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index 7ad15e4e..b3980cd9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -1,5 +1,7 @@ package com.lagradost.cloudstream3.ui.home +import android.os.Bundle +import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -7,6 +9,7 @@ import androidx.appcompat.widget.SearchView import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.fragment.app.Fragment import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding @@ -26,6 +29,7 @@ import com.lagradost.cloudstream3.databinding.FragmentHomeHeadTvBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.debugException import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLinear import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.selectHomepage @@ -47,114 +51,87 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView import com.lagradost.cloudstream3.utils.UIHelper.populateChips class HomeParentItemAdapterPreview( - items: MutableList, + override val fragment: Fragment, private val viewModel: HomeViewModel, -) : ParentItemAdapter(items, +) : ParentItemAdapter(fragment, id = "HomeParentItemAdapterPreview".hashCode(), clickCallback = { - viewModel.click(it) -}, moreInfoClickCallback = { - viewModel.popup(it) -}, expandCallback = { - viewModel.expand(it) -}) { - val headItems = 1 + viewModel.click(it) + }, moreInfoClickCallback = { + viewModel.popup(it) + }, expandCallback = { + viewModel.expand(it) + }) { + override val headers = 1 + override fun onCreateHeader(parent: ViewGroup): ViewHolderState { + val inflater = LayoutInflater.from(parent.context) + val binding = if (isLayout(TV or EMULATOR)) FragmentHomeHeadTvBinding.inflate( + inflater, + parent, + false + ) else FragmentHomeHeadBinding.inflate(inflater, parent, false) - companion object { - private const val VIEW_TYPE_HEADER = 2 - private const val VIEW_TYPE_ITEM = 1 - } + if (binding is FragmentHomeHeadTvBinding && isLayout(EMULATOR)) { + binding.homeBookmarkParentItemMoreInfo.isVisible = true - override fun getItemViewType(position: Int) = when (position) { - 0 -> VIEW_TYPE_HEADER - else -> VIEW_TYPE_ITEM - } + val marginInDp = 50 + val density = binding.horizontalScrollChips.context.resources.displayMetrics.density + val marginInPixels = (marginInDp * density).toInt() - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder) { - is HeaderViewHolder -> {} - else -> super.onBindViewHolder(holder, position - headItems) + val params = binding.horizontalScrollChips.layoutParams as ViewGroup.MarginLayoutParams + params.marginEnd = marginInPixels + binding.horizontalScrollChips.layoutParams = params + binding.homeWatchParentItemTitle.setCompoundDrawablesWithIntrinsicBounds( + null, + null, + ContextCompat.getDrawable( + parent.context, + R.drawable.ic_baseline_arrow_forward_24 + ), + null + ) } + + return HeaderViewHolder(binding, viewModel, fragment = fragment) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return when (viewType) { - VIEW_TYPE_HEADER -> { - val inflater = LayoutInflater.from(parent.context) - val binding = if (isLayout(TV or EMULATOR)) FragmentHomeHeadTvBinding.inflate( - inflater, - parent, - false - ) else FragmentHomeHeadBinding.inflate(inflater, parent, false) + override fun onBindHeader(holder: ViewHolderState) { + (holder as? HeaderViewHolder)?.bind() + } - if (binding is FragmentHomeHeadTvBinding && isLayout(EMULATOR)) { - binding.homeBookmarkParentItemMoreInfo.isVisible = true + private class HeaderViewHolder( + val binding: ViewBinding, val viewModel: HomeViewModel, fragment: Fragment, + ) : + ViewHolderState(binding) { - val marginInDp = 50 - val density = binding.horizontalScrollChips.context.resources.displayMetrics.density - val marginInPixels = (marginInDp * density).toInt() - - val params = binding.horizontalScrollChips.layoutParams as ViewGroup.MarginLayoutParams - params.marginEnd = marginInPixels - binding.horizontalScrollChips.layoutParams = params - binding.homeWatchParentItemTitle.setCompoundDrawablesWithIntrinsicBounds( - null, - null, - ContextCompat.getDrawable( - parent.context, - R.drawable.ic_baseline_arrow_forward_24 - ), - null - ) - } - - HeaderViewHolder( - binding, - viewModel, + override fun save(): Bundle = + Bundle().apply { + putParcelable( + "resumeRecyclerView", + resumeRecyclerView.layoutManager?.onSaveInstanceState() ) + putParcelable( + "bookmarkRecyclerView", + bookmarkRecyclerView.layoutManager?.onSaveInstanceState() + ) + //putInt("previewViewpager", previewViewpager.currentItem) } - VIEW_TYPE_ITEM -> super.onCreateViewHolder(parent, viewType) - else -> error("Unhandled viewType=$viewType") - } - } - - override fun getItemCount(): Int { - return super.getItemCount() + headItems - } - - override fun getItemId(position: Int): Long { - if (position == 0) return 0//previewData.hashCode().toLong() - return super.getItemId(position - headItems) - } - - override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) { - when (holder) { - is HeaderViewHolder -> { - holder.onViewDetachedFromWindow() + override fun restore(state: Bundle) { + state.getParcelable("resumeRecyclerView")?.let { recycle -> + resumeRecyclerView.layoutManager?.onRestoreInstanceState(recycle) } - - else -> super.onViewDetachedFromWindow(holder) - } - } - - override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { - when (holder) { - is HeaderViewHolder -> { - holder.onViewAttachedToWindow() + state.getParcelable("bookmarkRecyclerView")?.let { recycle -> + bookmarkRecyclerView.layoutManager?.onRestoreInstanceState(recycle) } - - else -> super.onViewAttachedToWindow(holder) + //state.getInt("previewViewpager").let { recycle -> + // previewViewpager.setCurrentItem(recycle,true) + //} } - } - class HeaderViewHolder - constructor( - val binding: ViewBinding, - val viewModel: HomeViewModel, - ) : RecyclerView.ViewHolder(binding.root) { - private var previewAdapter: HomeScrollAdapter = HomeScrollAdapter() - private var resumeAdapter: HomeChildItemAdapter = HomeChildItemAdapter( - ArrayList(), + val previewAdapter = HomeScrollAdapter(fragment = fragment) + private val resumeAdapter = HomeChildItemAdapter( + fragment, + id = "resumeAdapter".hashCode(), nextFocusUp = itemView.nextFocusUpId, nextFocusDown = itemView.nextFocusDownId ) { callback -> @@ -209,8 +186,9 @@ class HomeParentItemAdapterPreview( } } } - private var bookmarkAdapter: HomeChildItemAdapter = HomeChildItemAdapter( - ArrayList(), + private val bookmarkAdapter = HomeChildItemAdapter( + fragment, + id = "bookmarkAdapter".hashCode(), nextFocusUp = itemView.nextFocusUpId, nextFocusDown = itemView.nextFocusDownId ) { callback -> @@ -219,7 +197,10 @@ class HomeParentItemAdapterPreview( return@HomeChildItemAdapter } - (callback.view.context?.getActivity() as? MainActivity)?.loadPopup(callback.card, load = false) + (callback.view.context?.getActivity() as? MainActivity)?.loadPopup( + callback.card, + load = false + ) /* callback.view.context?.getActivity()?.showOptionSelectStringRes( callback.view, @@ -269,7 +250,6 @@ class HomeParentItemAdapterPreview( */ } - private val previewViewpager: ViewPager2 = itemView.findViewById(R.id.home_preview_viewpager) @@ -277,38 +257,24 @@ class HomeParentItemAdapterPreview( itemView.findViewById(R.id.home_preview_viewpager_text) // private val previewHeader: FrameLayout = itemView.findViewById(R.id.home_preview) - private var resumeHolder: View = itemView.findViewById(R.id.home_watch_holder) - private var resumeRecyclerView: RecyclerView = + private val resumeHolder: View = itemView.findViewById(R.id.home_watch_holder) + private val resumeRecyclerView: RecyclerView = itemView.findViewById(R.id.home_watch_child_recyclerview) - private var bookmarkHolder: View = itemView.findViewById(R.id.home_bookmarked_holder) - private var bookmarkRecyclerView: RecyclerView = + private val bookmarkHolder: View = itemView.findViewById(R.id.home_bookmarked_holder) + private val bookmarkRecyclerView: RecyclerView = itemView.findViewById(R.id.home_bookmarked_child_recyclerview) - private var homeAccount: View? = - itemView.findViewById(R.id.home_preview_switch_account) - private var alternativeHomeAccount: View? = + private val homeAccount: View? = itemView.findViewById(R.id.home_preview_switch_account) + private val alternativeHomeAccount: View? = itemView.findViewById(R.id.alternative_switch_account) - private var topPadding: View? = itemView.findViewById(R.id.home_padding) + private val topPadding: View? = itemView.findViewById(R.id.home_padding) - private var alternativeAccountPadding: View? = itemView.findViewById(R.id.alternative_account_padding) + private val alternativeAccountPadding: View? = + itemView.findViewById(R.id.alternative_account_padding) private val homeNonePadding: View = itemView.findViewById(R.id.home_none_padding) - private val previewCallback: ViewPager2.OnPageChangeCallback = - object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - previewAdapter.apply { - if (position >= itemCount - 1 && hasMoreItems) { - hasMoreItems = false // don't make two requests - viewModel.loadMoreHomeScrollResponses() - } - } - val item = previewAdapter.getItem(position) ?: return - onSelect(item, position) - } - } - fun onSelect(item: LoadResponse, position: Int) { (binding as? FragmentHomeHeadTvBinding)?.apply { homePreviewDescription.isGone = @@ -381,14 +347,14 @@ class HomeParentItemAdapterPreview( homePreviewBookmark.setOnClickListener { fab -> fab.context.getActivity()?.showBottomDialog( - WatchType.values() + WatchType.entries .map { fab.context.getString(it.stringRes) } .toList(), DataStoreHelper.getResultWatchState(id).ordinal, fab.context.getString(R.string.action_add_to_bookmarks), showApply = false, {}) { - val newValue = WatchType.values()[it] + val newValue = WatchType.entries[it] ResultViewModel2().updateWatchStatus( newValue, @@ -413,38 +379,22 @@ class HomeParentItemAdapterPreview( } } - fun onViewDetachedFromWindow() { - previewViewpager.unregisterOnPageChangeCallback(previewCallback) - } - - fun onViewAttachedToWindow() { - previewViewpager.registerOnPageChangeCallback(previewCallback) - - binding.root.findViewTreeLifecycleOwner()?.apply { - observe(viewModel.preview) { - updatePreview(it) - } - if (binding is FragmentHomeHeadTvBinding) { - observe(viewModel.apiName) { name -> - binding.homePreviewChangeApi.text = name - } - } - observe(viewModel.resumeWatching) { - updateResume(it) - } - observe(viewModel.bookmarks) { - updateBookmarks(it) - } - observe(viewModel.availableWatchStatusTypes) { (checked, visible) -> - for ((chip, watch) in toggleList) { - chip.apply { - isVisible = visible.contains(watch) - isChecked = checked.contains(watch) + private val previewCallback: ViewPager2.OnPageChangeCallback = + object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + previewAdapter.apply { + if (position >= itemCount - 1 && hasMoreItems) { + hasMoreItems = false // don't make two requests + viewModel.loadMoreHomeScrollResponses() } } - toggleListHolder?.isGone = visible.isEmpty() + val item = previewAdapter.getItem(position) ?: return + onSelect(item, position) } - } ?: debugException { "Expected findViewTreeLifecycleOwner" } + } + + override fun onViewDetachedFromWindow() { + previewViewpager.unregisterOnPageChangeCallback(previewCallback) } private val toggleList = listOf>( @@ -457,6 +407,8 @@ class HomeParentItemAdapterPreview( private val toggleListHolder: ChipGroup? = itemView.findViewById(R.id.home_type_holder) + fun bind() = Unit + init { previewViewpager.setPageTransformer(HomeScrollTransformer()) @@ -563,7 +515,8 @@ class HomeParentItemAdapterPreview( when (preview) { is Resource.Success -> { - if (!previewAdapter.setItems( + previewAdapter.submitList(preview.value.second) + /*if (!.setItems( preview.value.second, preview.value.first ) @@ -575,15 +528,16 @@ class HomeParentItemAdapterPreview( previewViewpager.fakeDragBy(1f) previewViewpager.endFakeDrag() previewCallback.onPageSelected(0) - previewViewpager.isVisible = true - previewViewpagerText.isVisible = true - alternativeAccountPadding?.isVisible = false //previewHeader.isVisible = true - } + }*/ + + previewViewpager.isVisible = true + previewViewpagerText.isVisible = true + alternativeAccountPadding?.isVisible = false } else -> { - previewAdapter.setItems(listOf(), false) + previewAdapter.submitList(listOf()) previewViewpager.setCurrentItem(0, false) previewViewpager.isVisible = false previewViewpagerText.isVisible = false @@ -595,7 +549,7 @@ class HomeParentItemAdapterPreview( private fun updateResume(resumeWatching: List) { resumeHolder.isVisible = resumeWatching.isNotEmpty() - resumeAdapter.updateList(resumeWatching) + resumeAdapter.submitList(resumeWatching) if ( binding is FragmentHomeHeadBinding || @@ -625,7 +579,7 @@ class HomeParentItemAdapterPreview( private fun updateBookmarks(data: Pair>) { val (visible, list) = data bookmarkHolder.isVisible = visible - bookmarkAdapter.updateList(list) + bookmarkAdapter.submitList(list) if ( binding is FragmentHomeHeadBinding || @@ -655,5 +609,35 @@ class HomeParentItemAdapterPreview( } } } + + override fun onViewAttachedToWindow() { + previewViewpager.registerOnPageChangeCallback(previewCallback) + + binding.root.findViewTreeLifecycleOwner()?.apply { + observe(viewModel.preview) { + updatePreview(it) + } + if (binding is FragmentHomeHeadTvBinding) { + observe(viewModel.apiName) { name -> + binding.homePreviewChangeApi.text = name + } + } + observe(viewModel.resumeWatching) { + updateResume(it) + } + observe(viewModel.bookmarks) { + updateBookmarks(it) + } + observe(viewModel.availableWatchStatusTypes) { (checked, visible) -> + for ((chip, watch) in toggleList) { + chip.apply { + isVisible = visible.contains(watch) + isChecked = checked.contains(watch) + } + } + toggleListHolder?.isGone = visible.isEmpty() + } + } ?: debugException { "Expected findViewTreeLifecycleOwner" } + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index f0542b77..e50ea954 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -4,43 +4,23 @@ import android.content.res.Configuration import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isGone -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import androidx.viewbinding.ViewBinding +import androidx.fragment.app.Fragment import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding +import com.lagradost.cloudstream3.ui.BaseAdapter +import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.UIHelper.setImage -class HomeScrollAdapter : RecyclerView.Adapter() { - private var items: MutableList = mutableListOf() +class HomeScrollAdapter( + fragment: Fragment +) : BaseAdapter(fragment, "HomeScrollAdapter".hashCode()) { var hasMoreItems: Boolean = false - fun getItem(position: Int): LoadResponse? { - return items.getOrNull(position) - } - - fun setItems(newItems: List, hasNext: Boolean): Boolean { - val isSame = newItems.firstOrNull()?.url == items.firstOrNull()?.url - hasMoreItems = hasNext - - val diffResult = DiffUtil.calculateDiff( - HomeScrollDiffCallback(this.items, newItems) - ) - - items.clear() - items.addAll(newItems) - - - diffResult.dispatchUpdatesTo(this) - - return isSame - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + override fun onCreateContent(parent: ViewGroup): ViewHolderState { val inflater = LayoutInflater.from(parent.context) val binding = if (isLayout(TV or EMULATOR)) { HomeScrollViewTvBinding.inflate(inflater, parent, false) @@ -48,70 +28,37 @@ class HomeScrollAdapter : RecyclerView.Adapter() { HomeScrollViewBinding.inflate(inflater, parent, false) } - return CardViewHolder( - binding, - //forceHorizontalPosters - ) + return ViewHolderState(binding) } - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder) { - is CardViewHolder -> { - holder.bind(items[position]) + override fun onBindContent( + holder: ViewHolderState, + item: LoadResponse, + position: Int, + ) { + val binding = holder.view + val itemView = holder.itemView + val isHorizontal = + binding is HomeScrollViewTvBinding || itemView.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + + val posterUrl = + if (isHorizontal) item.backgroundPosterUrl ?: item.posterUrl else item.posterUrl + ?: item.backgroundPosterUrl + + when (binding) { + is HomeScrollViewBinding -> { + binding.homeScrollPreview.setImage(posterUrl) + binding.homeScrollPreviewTags.apply { + text = item.tags?.joinToString(" • ") ?: "" + isGone = item.tags.isNullOrEmpty() + maxLines = 2 + } + binding.homeScrollPreviewTitle.text = item.name + } + + is HomeScrollViewTvBinding -> { + binding.homeScrollPreview.setImage(posterUrl) } } } - - class CardViewHolder - constructor( - val binding: ViewBinding, - //private val forceHorizontalPosters: Boolean? = null - ) : - RecyclerView.ViewHolder(binding.root) { - - fun bind(card: LoadResponse) { - val isHorizontal = - binding is HomeScrollViewTvBinding || itemView.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE - - val posterUrl = - if (isHorizontal) card.backgroundPosterUrl ?: card.posterUrl else card.posterUrl - ?: card.backgroundPosterUrl - - when (binding) { - is HomeScrollViewBinding -> { - binding.homeScrollPreview.setImage(posterUrl) - binding.homeScrollPreviewTags.apply { - text = card.tags?.joinToString(" • ") ?: "" - isGone = card.tags.isNullOrEmpty() - maxLines = 2 - } - binding.homeScrollPreviewTitle.text = card.name - } - - is HomeScrollViewTvBinding -> { - binding.homeScrollPreview.setImage(posterUrl) - } - } - } - } - - class HomeScrollDiffCallback( - private val oldList: List, - private val newList: List - ) : - DiffUtil.Callback() { - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = - oldList[oldItemPosition].url == newList[newItemPosition].url - - override fun getOldListSize() = oldList.size - - override fun getNewListSize() = newList.size - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = - oldList[oldItemPosition] == newList[newItemPosition] - } - - override fun getItemCount(): Int { - return items.size - } } \ No newline at end of file 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 6735a350..d79c44b7 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 @@ -47,6 +47,9 @@ import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper import com.lagradost.cloudstream3.ui.result.setText import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.settings.Globals +import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR +import com.lagradost.cloudstream3.ui.settings.Globals.TV +import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.SettingsFragment import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.DataStoreHelper @@ -78,7 +81,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { private var isVerticalOrientation: Boolean = false protected open var lockRotation = true protected open var isFullScreenPlayer = true - protected open var isTv = false protected var playerBinding: PlayerCustomLayoutBinding? = null private var durationMode : Boolean by UserPreferenceDelegate("duration_mode", false) @@ -1205,7 +1207,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { // netflix capture back and hide ~monke KeyEvent.KEYCODE_BACK -> { - if (isShowing && isTv) { + if (isShowing && isLayout(TV or EMULATOR)) { onClickChange() return true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt index 26cf9918..e9e00736 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt @@ -174,7 +174,7 @@ class QuickSearchFragment : Fragment() { } } else { binding?.quickSearchMasterRecycler?.adapter = - ParentItemAdapter(mutableListOf(), { callback -> + ParentItemAdapter(fragment = this, id = "quickSearchMasterRecycler".hashCode(), { callback -> SearchHelper.handleSearchClickCallback(callback) //when (callback.action) { //SEARCH_ACTION_LOAD -> { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 4b4700ce..24e87d30 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -46,6 +46,7 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.ui.BaseAdapter import com.lagradost.cloudstream3.ui.home.HomeFragment import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan @@ -161,7 +162,8 @@ class SearchFragment : Fragment() { **/ fun search(query: String?) { if (query == null) return - + // don't resume state from prev search + (binding?.searchMasterRecycler?.adapter as? BaseAdapter<*,*>)?.clear() context?.let { ctx -> val default = enumValues().sorted().filter { it != TvType.NSFW } .map { it.ordinal.toString() }.toSet() @@ -506,8 +508,8 @@ class SearchFragment : Fragment() { }*/ //main_search.onActionViewExpanded()*/ - val masterAdapter: RecyclerView.Adapter = - ParentItemAdapter(mutableListOf(), { callback -> + val masterAdapter = + ParentItemAdapter(fragment = this, id = "masterAdapter".hashCode(), { callback -> SearchHelper.handleSearchClickCallback(callback) }, { item -> bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = {