made inf scrolling better + fixed search bug

This commit is contained in:
LagradOst 2022-07-30 17:30:23 +02:00
parent 625ff8c910
commit d3b3091fe0
10 changed files with 179 additions and 47 deletions

View file

@ -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<Search>()
?: throw ErrorLoadingException("No Media")

View file

@ -24,6 +24,7 @@ class HomeChildItemAdapter(
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var isHorizontal: Boolean = false
var hasNext : Boolean = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = overrideLayout

View file

@ -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<TextView>(R.id.home_expanded_text)!!
val item = expand.list
title.text = item.name
val recycle =
bottomSheetDialogBuilder.findViewById<AutofitRecyclerView>(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

View file

@ -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<HomeViewModel.ExpandableHomepageList>,
private val clickCallback: (SearchClickCallback) -> Unit,
private val moreInfoClickCallback: (HomePageList) -> Unit,
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
private val expandCallback: ((String) -> Unit)? = null,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
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<HomeViewModel.ExpandableHomepageList>) {
fun updateList(
newList: MutableList<HomeViewModel.ExpandableHomepageList>,
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)
}
}
}

View file

@ -158,9 +158,8 @@ class HomeViewModel : ViewModel() {
val lock: MutableSet<String> = 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 {

View file

@ -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
})

View file

@ -27,6 +27,7 @@ class SearchAdapter(
private val resView: AutofitRecyclerView,
private val clickCallback: (SearchClickCallback) -> Unit,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
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

View file

@ -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
}

View file

@ -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<OnGoingSearch>()
_currentSearch.postValue(ArrayList())
withContext(Dispatchers.IO) { // This interrupts UI otherwise
val currentList = ArrayList<OnGoingSearch>()
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<SearchResponse>()
val nestedList =
currentList.map { it.data }
.filterIsInstance<Resource.Success<List<SearchResponse>>>().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<SearchResponse>()
val nestedList =
currentList.map { it.data }
.filterIsInstance<Resource.Success<List<SearchResponse>>>().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))
}
}
}

View file

@ -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 {