diff --git a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AniflixProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AniflixProvider.kt index 0d7f82fc..5500b57b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AniflixProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/animeproviders/AniflixProvider.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.animeproviders import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId -import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName import java.net.URLDecoder @@ -75,7 +74,6 @@ class AniflixProvider : MainAPI() { val token = getToken() val url = "$mainUrl/_next/data/$token/search.json?keyword=$query" val response = app.get(url) - println("resp: $url ===> ${response.text}") val searchResponse = response.parsedSafe() ?: throw ErrorLoadingException("No Media") 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 7a4a43a6..7b3e430d 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 @@ -24,6 +24,7 @@ class HomeChildItemAdapter( ) : RecyclerView.Adapter() { var isHorizontal: Boolean = false + var hasNext : Boolean = false override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val layout = overrideLayout 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 f6d0babf..b1dde1d7 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 @@ -22,6 +22,7 @@ import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSnapHelper +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.* @@ -46,6 +47,8 @@ import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallba import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult +import com.lagradost.cloudstream3.utils.AppUtils.setMaxViewPoolSize +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper @@ -119,11 +122,27 @@ class HomeFragment : Fragment() { val errorProfilePic = errorProfilePics.random() - fun Activity.loadHomepageList(item: HomePageList, deleteCallback: (() -> Unit)? = null) { + fun Activity.loadHomepageList( + item: HomePageList, + deleteCallback: (() -> Unit)? = null, + ) { + loadHomepageList( + expand = HomeViewModel.ExpandableHomepageList(item, 1, false), + deleteCallback = deleteCallback, + expandCallback = null + ) + } + + fun Activity.loadHomepageList( + expand: HomeViewModel.ExpandableHomepageList, + deleteCallback: (() -> Unit)? = null, + expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null + ) { val context = this val bottomSheetDialogBuilder = BottomSheetDialog(context) bottomSheetDialogBuilder.setContentView(R.layout.home_episodes_expanded) val title = bottomSheetDialogBuilder.findViewById(R.id.home_expanded_text)!! + val item = expand.list title.text = item.name val recycle = bottomSheetDialogBuilder.findViewById(R.id.home_expanded_recycler)!! @@ -176,8 +195,36 @@ class HomeFragment : Fragment() { if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) { bottomSheetDialogBuilder.dismissSafe(this) } + }.apply { + hasNext = expand.hasNext } + recycle.addOnScrollListener(object : RecyclerView.OnScrollListener() { + var expandCount = 0 + val name = expand.list.name + + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + + val adapter = recyclerView.adapter + if (adapter !is SearchAdapter) return + + val count = adapter.itemCount + val currentHasNext = adapter.hasNext + if (!recyclerView.canScrollVertically(1) && currentHasNext && expandCount != count) { + expandCount = count + ioSafe { + expandCallback?.invoke(name)?.let { newExpand -> + (recyclerView.adapter as? SearchAdapter?)?.apply { + hasNext = newExpand.hasNext + updateList(newExpand.list.list) + } + } + } + } + } + }) + val spanListener = { span: Int -> recycle.spanCount = span //(recycle.adapter as SearchAdapter).notifyDataSetChanged() @@ -537,7 +584,10 @@ class HomeFragment : Fragment() { listHomepageItems.clear() // println("ITEMCOUNT: ${d.values.size} ${home_master_recycler?.adapter?.itemCount}") - (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(d.values.toMutableList()) + (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList( + d.values.toMutableList(), + home_master_recycler + ) home_loading?.isVisible = false home_loading_error?.isVisible = false @@ -843,15 +893,18 @@ class HomeFragment : Fragment() { ParentItemAdapter(mutableListOf(), { callback -> homeHandleSearch(callback) }, { item -> - activity?.loadHomepageList(item) + activity?.loadHomepageList(item, expandCallback = { + homeViewModel.expandAndReturn(it) + }) }, { name -> homeViewModel.expand(name) }) + home_master_recycler?.setMaxViewPoolSize(0, Int.MAX_VALUE) home_master_recycler.layoutManager = object : LinearLayoutManager(context) { override fun supportsPredictiveItemAnimations(): Boolean { return false } - }; // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() } + } // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() } if (context?.isTvSettings() == false) { LinearSnapHelper().attachToRecyclerView(home_main_poster_recyclerview) // snap 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 15b242ed..170bab4c 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 @@ -6,10 +6,12 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.TextView import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.search.SearchClickCallback +import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import kotlinx.android.synthetic.main.homepage_parent.view.* @@ -17,11 +19,12 @@ import kotlinx.android.synthetic.main.homepage_parent.view.* class ParentItemAdapter( private var items: MutableList, private val clickCallback: (SearchClickCallback) -> Unit, - private val moreInfoClickCallback: (HomePageList) -> Unit, + private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit, private val expandCallback: ((String) -> Unit)? = null, ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, i: Int): ParentViewHolder { + //println("onCreateViewHolder $i") val layout = if (parent.context.isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent return ParentViewHolder( @@ -33,6 +36,8 @@ class ParentItemAdapter( } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + //println("onBindViewHolder $position") + when (holder) { is ParentViewHolder -> { holder.bind(items[position]) @@ -55,14 +60,25 @@ class ParentItemAdapter( } @JvmName("updateListExpandableHomepageList") - fun updateList(newList: MutableList) { + 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, newList) + SearchDiffCallback(items, new) ) items.clear() - items.addAll(newList.map { it.copy(list = it.list.copy()) }) // I have to do this otherwise it is a "copy" and dispatchUpdatesTo wont work + items.addAll(new) - /*val mAdapter = this + val mAdapter = this diffResult.dispatchUpdatesTo(object : ListUpdateCallback { override fun onInserted(position: Int, count: Int) { mAdapter.notifyItemRangeInserted(position, count) @@ -77,18 +93,44 @@ class ParentItemAdapter( } override fun onChanged(position: Int, count: Int, payload: Any?) { - mAdapter.notifyItemRangeChanged(position, count, payload) - } - })*/ + // 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 mAdapter.itemCount) { + val viewHolder = getChildViewHolder(getChildAt(i)) + val absolutePosition = viewHolder.absoluteAdapterPosition + if (absolutePosition >= position && absolutePosition < position + count) { + val expand = items.getOrNull(absolutePosition) ?: continue + if (viewHolder is ParentViewHolder) { + missingUpdates -= absolutePosition + if (viewHolder.title.text == expand.list.name) { + viewHolder.update(expand) + } else { + viewHolder.bind(expand) + } + } + } + } - diffResult.dispatchUpdatesTo(this) + // just in case some item did not get updated + for (i in missingUpdates) { + mAdapter.notifyItemChanged(i, payload) + } + } ?: run { // in case we don't have a nice + mAdapter.notifyItemRangeChanged(position, count, payload) + } + } + }) + + //diffResult.dispatchUpdatesTo(this) } class ParentViewHolder constructor( itemView: View, private val clickCallback: (SearchClickCallback) -> Unit, - private val moreInfoClickCallback: (HomePageList) -> Unit, + private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit, private val expandCallback: ((String) -> Unit)? = null, ) : RecyclerView.ViewHolder(itemView) { @@ -96,6 +138,23 @@ class ParentItemAdapter( val recyclerView: RecyclerView = itemView.home_child_recyclerview private val moreInfo: FrameLayout? = itemView.home_child_more_info + 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 + } + } + } + fun bind(expand: HomeViewModel.ExpandableHomepageList) { val info = expand.list recyclerView.adapter = HomeChildItemAdapter( @@ -105,6 +164,7 @@ class ParentItemAdapter( nextFocusDown = recyclerView.nextFocusDownId, ).apply { isHorizontal = info.isHorizontalImages + hasNext = expand.hasNext } title.text = info.name @@ -116,8 +176,12 @@ class ParentItemAdapter( override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) - val count = recyclerView.adapter?.itemCount ?: return - if (!recyclerView.canScrollHorizontally(1) && expand.hasNext && expandCount != count) { + val adapter = recyclerView.adapter + if (adapter !is HomeChildItemAdapter) return + + val count = adapter.itemCount + val hasNext = adapter.hasNext + if (!recyclerView.canScrollHorizontally(1) && hasNext && expandCount != count) { expandCount = count expandCallback?.invoke(name) } @@ -127,7 +191,7 @@ class ParentItemAdapter( //(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged() moreInfo?.setOnClickListener { - moreInfoClickCallback.invoke(info) + moreInfoClickCallback.invoke(expand) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index 22b5e90d..14290fca 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -158,9 +158,8 @@ class HomeViewModel : ViewModel() { val lock: MutableSet = mutableSetOf() - // this is soo over engineered, but idk how I can make it clean without making the main api harder to use :pensive: - fun expand(name: String) = viewModelScope.launch { - if (lock.contains(name)) return@launch + suspend fun expandAndReturn(name: String) : ExpandableHomepageList? { + if (lock.contains(name)) return null lock += name repo?.apply { @@ -183,11 +182,8 @@ class HomeViewModel : ViewModel() { "Expanded contained an item that was previously already in the list\n${list.name} = ${this.list.list}\n${newList.name} = ${newList.list}" } - val before = list.list.size this.list.list += newList.list this.list.list.distinctBy { it.url } // just to be sure we are not adding the same shit for some reason - expandable[key] = this - val after = list.list.size } ?: debugWarning { "Expanded an item not in main load named $key, current list is ${expandable.keys}" } @@ -201,6 +197,13 @@ class HomeViewModel : ViewModel() { } lock -= name + + return expandable[name] + } + + // this is soo over engineered, but idk how I can make it clean without making the main api harder to use :pensive: + fun expand(name: String) = viewModelScope.launch { + expandAndReturn(name) } private fun load(api: MainAPI?) = viewModelScope.launch { 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 7fea40d0..df53f209 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 @@ -27,7 +27,6 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageLis import com.lagradost.cloudstream3.ui.home.ParentItemAdapter import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchClickCallback -import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse import com.lagradost.cloudstream3.ui.search.SearchHelper import com.lagradost.cloudstream3.ui.search.SearchViewModel import com.lagradost.cloudstream3.utils.UIHelper @@ -173,7 +172,7 @@ class QuickSearchFragment : Fragment() { updateList(list.map { ongoing -> val ongoingList = HomePageList( ongoing.apiName, - if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList() + if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList() ) ongoingList }) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index fe442e2b..c2523931 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -27,6 +27,7 @@ class SearchAdapter( private val resView: AutofitRecyclerView, private val clickCallback: (SearchClickCallback) -> Unit, ) : RecyclerView.Adapter() { + var hasNext : Boolean = false override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val layout = if(parent.context.IsBottomLayout()) R.layout.search_result_grid_expanded else R.layout.search_result_grid 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 fc2d1064..d05d8807 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 @@ -426,7 +426,7 @@ class SearchFragment : Fragment() { val newItems = list.map { ongoing -> val ongoingList = HomePageList( ongoing.apiName, - if (ongoing.data is Resource.Success) ongoing.data.value.filterSearchResponse() else ArrayList() + if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList() ) ongoingList } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index e8e37b9d..e02d37f8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -43,6 +43,7 @@ class SearchViewModel : ViewModel() { _currentSearch.postValue(emptyList()) } + private var currentSearchIndex = 0 private var onGoingSearch: Job? = null fun searchAndCancel( query: String, @@ -50,6 +51,7 @@ class SearchViewModel : ViewModel() { ignoreSettings: Boolean = false, isQuickSearch: Boolean = false, ) { + currentSearchIndex++ onGoingSearch?.cancel() onGoingSearch = search(query, providersActive, ignoreSettings, isQuickSearch) } @@ -70,6 +72,7 @@ class SearchViewModel : ViewModel() { isQuickSearch: Boolean = false, ) = viewModelScope.launch { + val currentIndex = currentSearchIndex if (query.length <= 1) { clearSearch() return@launch @@ -91,40 +94,44 @@ class SearchViewModel : ViewModel() { _searchResponse.postValue(Resource.Loading()) - val currentList = ArrayList() _currentSearch.postValue(ArrayList()) withContext(Dispatchers.IO) { // This interrupts UI otherwise + val currentList = ArrayList() + repos.filter { a -> (ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name))) && (!isQuickSearch || a.hasQuickSearch) }.apmap { a -> // Parallel val search = if (isQuickSearch) a.quickSearch(query) else a.search(query) + if(currentSearchIndex != currentIndex) return@apmap currentList.add(OnGoingSearch(a.name, search)) _currentSearch.postValue(currentList) } - } - _currentSearch.postValue(currentList) - val list = ArrayList() - val nestedList = - currentList.map { it.data } - .filterIsInstance>>().map { it.value } + if(currentSearchIndex != currentIndex) return@withContext // this should prevent rewrite of existing data bug - // I do it this way to move the relevant search results to the top - var index = 0 - while (true) { - var added = 0 - for (sublist in nestedList) { - if (sublist.size > index) { - list.add(sublist[index]) - added++ + _currentSearch.postValue(currentList) + val list = ArrayList() + val nestedList = + currentList.map { it.data } + .filterIsInstance>>().map { it.value } + + // I do it this way to move the relevant search results to the top + var index = 0 + while (true) { + var added = 0 + for (sublist in nestedList) { + if (sublist.size > index) { + list.add(sublist[index]) + added++ + } } + if (added == 0) break + index++ } - if (added == 0) break - index++ - } - _searchResponse.postValue(Resource.Success(list)) + _searchResponse.postValue(Resource.Success(list)) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index bdfc7b7d..5b7223d3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -26,6 +26,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.text.HtmlCompat import androidx.core.text.toSpanned +import androidx.recyclerview.widget.RecyclerView import androidx.tvprovider.media.tv.PreviewChannelHelper import androidx.tvprovider.media.tv.TvContractCompat import androidx.tvprovider.media.tv.WatchNextProgram @@ -52,6 +53,11 @@ import java.net.URL import java.net.URLDecoder object AppUtils { + fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) { + for (i in 0..maxViewTypeId) + recycledViewPool.setMaxRecycledViews(i, maxPoolSize) + } + //fun Context.deleteFavorite(data: SearchResponse) { // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return // normalSafeApiCall {