mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-27 23:40:33 +05:30
refactor: use paging adapter for channel content items
This commit is contained in:
parent
2c73287d71
commit
4cde38504a
@ -1,176 +0,0 @@
|
|||||||
package com.github.libretube.ui.adapters
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.core.view.isGone
|
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
|
||||||
import com.github.libretube.R
|
|
||||||
import com.github.libretube.api.JsonHelper
|
|
||||||
import com.github.libretube.api.obj.ContentItem
|
|
||||||
import com.github.libretube.api.obj.StreamItem
|
|
||||||
import com.github.libretube.constants.IntentData
|
|
||||||
import com.github.libretube.databinding.ChannelRowBinding
|
|
||||||
import com.github.libretube.databinding.PlaylistsRowBinding
|
|
||||||
import com.github.libretube.databinding.VideoRowBinding
|
|
||||||
import com.github.libretube.enums.PlaylistType
|
|
||||||
import com.github.libretube.extensions.formatShort
|
|
||||||
import com.github.libretube.extensions.toID
|
|
||||||
import com.github.libretube.helpers.ImageHelper
|
|
||||||
import com.github.libretube.helpers.NavigationHelper
|
|
||||||
import com.github.libretube.ui.adapters.callbacks.DiffUtilItemCallback
|
|
||||||
import com.github.libretube.ui.base.BaseActivity
|
|
||||||
import com.github.libretube.ui.extensions.setFormattedDuration
|
|
||||||
import com.github.libretube.ui.extensions.setWatchProgressLength
|
|
||||||
import com.github.libretube.ui.extensions.setupSubscriptionButton
|
|
||||||
import com.github.libretube.ui.sheets.ChannelOptionsBottomSheet
|
|
||||||
import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
|
|
||||||
import com.github.libretube.ui.sheets.VideoOptionsBottomSheet
|
|
||||||
import com.github.libretube.ui.viewholders.SearchViewHolder
|
|
||||||
import com.github.libretube.util.TextUtils
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
|
|
||||||
// TODO: Replace with SearchResultsAdapter when migrating the channel fragment to use Paging as well
|
|
||||||
class SearchChannelAdapter : ListAdapter<ContentItem, SearchViewHolder>(
|
|
||||||
DiffUtilItemCallback(
|
|
||||||
areItemsTheSame = { oldItem, newItem -> oldItem.url == newItem.url },
|
|
||||||
areContentsTheSame = { _, _ -> true },
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
|
|
||||||
val layoutInflater = LayoutInflater.from(parent.context)
|
|
||||||
|
|
||||||
return when (viewType) {
|
|
||||||
0 -> SearchViewHolder(VideoRowBinding.inflate(layoutInflater, parent, false))
|
|
||||||
1 -> SearchViewHolder(ChannelRowBinding.inflate(layoutInflater, parent, false))
|
|
||||||
2 -> SearchViewHolder(PlaylistsRowBinding.inflate(layoutInflater, parent, false))
|
|
||||||
else -> throw IllegalArgumentException("Invalid type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: SearchViewHolder, position: Int) {
|
|
||||||
val searchItem = currentList[position]
|
|
||||||
|
|
||||||
val videoRowBinding = holder.videoRowBinding
|
|
||||||
val channelRowBinding = holder.channelRowBinding
|
|
||||||
val playlistRowBinding = holder.playlistRowBinding
|
|
||||||
|
|
||||||
if (videoRowBinding != null) {
|
|
||||||
bindVideo(searchItem, videoRowBinding, position)
|
|
||||||
} else if (channelRowBinding != null) {
|
|
||||||
bindChannel(searchItem, channelRowBinding)
|
|
||||||
} else if (playlistRowBinding != null) {
|
|
||||||
bindPlaylist(searchItem, playlistRowBinding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
|
||||||
return when (currentList[position].type) {
|
|
||||||
StreamItem.TYPE_STREAM -> 0
|
|
||||||
StreamItem.TYPE_CHANNEL -> 1
|
|
||||||
StreamItem.TYPE_PLAYLIST -> 2
|
|
||||||
else -> 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindVideo(item: ContentItem, binding: VideoRowBinding, position: Int) {
|
|
||||||
binding.apply {
|
|
||||||
ImageHelper.loadImage(item.thumbnail, thumbnail)
|
|
||||||
thumbnailDuration.setFormattedDuration(item.duration, item.isShort, item.uploaded)
|
|
||||||
videoTitle.text = item.title
|
|
||||||
videoInfo.text = TextUtils.formatViewsString(root.context, item.views, item.uploaded)
|
|
||||||
|
|
||||||
channelContainer.isGone = true
|
|
||||||
|
|
||||||
root.setOnClickListener {
|
|
||||||
NavigationHelper.navigateVideo(root.context, item.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
val videoId = item.url.toID()
|
|
||||||
val activity = (root.context as BaseActivity)
|
|
||||||
val fragmentManager = activity.supportFragmentManager
|
|
||||||
root.setOnLongClickListener {
|
|
||||||
fragmentManager.setFragmentResultListener(
|
|
||||||
VideoOptionsBottomSheet.VIDEO_OPTIONS_SHEET_REQUEST_KEY,
|
|
||||||
activity
|
|
||||||
) { _, _ ->
|
|
||||||
notifyItemChanged(position)
|
|
||||||
}
|
|
||||||
val sheet = VideoOptionsBottomSheet()
|
|
||||||
val contentItemString = JsonHelper.json.encodeToString(item)
|
|
||||||
val streamItem: StreamItem = JsonHelper.json.decodeFromString(contentItemString)
|
|
||||||
sheet.arguments = bundleOf(IntentData.streamItem to streamItem)
|
|
||||||
sheet.show(fragmentManager, SearchChannelAdapter::class.java.name)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
channelContainer.setOnClickListener {
|
|
||||||
NavigationHelper.navigateChannel(root.context, item.uploaderUrl)
|
|
||||||
}
|
|
||||||
watchProgress.setWatchProgressLength(videoId, item.duration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindChannel(item: ContentItem, binding: ChannelRowBinding) {
|
|
||||||
binding.apply {
|
|
||||||
ImageHelper.loadImage(item.thumbnail, searchChannelImage, true)
|
|
||||||
searchChannelName.text = item.name
|
|
||||||
|
|
||||||
val subscribers = item.subscribers.formatShort()
|
|
||||||
searchViews.text = if (item.subscribers >= 0 && item.videos >= 0) {
|
|
||||||
root.context.getString(R.string.subscriberAndVideoCounts, subscribers, item.videos)
|
|
||||||
} else if (item.subscribers >= 0) {
|
|
||||||
root.context.getString(R.string.subscribers, subscribers)
|
|
||||||
} else if (item.videos >= 0) {
|
|
||||||
root.context.getString(R.string.videoCount, item.videos)
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
root.setOnClickListener {
|
|
||||||
NavigationHelper.navigateChannel(root.context, item.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
var subscribed = false
|
|
||||||
binding.searchSubButton.setupSubscriptionButton(item.url.toID(), item.name?.toID()) {
|
|
||||||
subscribed = it
|
|
||||||
}
|
|
||||||
|
|
||||||
root.setOnLongClickListener {
|
|
||||||
val channelOptionsSheet = ChannelOptionsBottomSheet()
|
|
||||||
channelOptionsSheet.arguments = bundleOf(
|
|
||||||
IntentData.channelId to item.url.toID(),
|
|
||||||
IntentData.channelName to item.name,
|
|
||||||
IntentData.isSubscribed to subscribed
|
|
||||||
)
|
|
||||||
channelOptionsSheet.show((root.context as BaseActivity).supportFragmentManager)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindPlaylist(item: ContentItem, binding: PlaylistsRowBinding) {
|
|
||||||
binding.apply {
|
|
||||||
ImageHelper.loadImage(item.thumbnail, playlistThumbnail)
|
|
||||||
if (item.videos >= 0) videoCount.text = item.videos.toString()
|
|
||||||
playlistTitle.text = item.name
|
|
||||||
playlistDescription.text = item.uploaderName
|
|
||||||
root.setOnClickListener {
|
|
||||||
NavigationHelper.navigatePlaylist(root.context, item.url, PlaylistType.PUBLIC)
|
|
||||||
}
|
|
||||||
|
|
||||||
root.setOnLongClickListener {
|
|
||||||
val sheet = PlaylistOptionsBottomSheet()
|
|
||||||
sheet.arguments = bundleOf(
|
|
||||||
IntentData.playlistId to item.url.toID(),
|
|
||||||
IntentData.playlistName to item.name.orEmpty(),
|
|
||||||
IntentData.playlistType to PlaylistType.PUBLIC
|
|
||||||
)
|
|
||||||
sheet.show(
|
|
||||||
(root.context as BaseActivity).supportFragmentManager,
|
|
||||||
PlaylistOptionsBottomSheet::class.java.name
|
|
||||||
)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,7 @@ package com.github.libretube.ui.adapters
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.paging.PagingDataAdapter
|
import androidx.paging.PagingDataAdapter
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
@ -91,10 +92,12 @@ class SearchResultsAdapter(
|
|||||||
private fun bindVideo(item: ContentItem, binding: VideoRowBinding, position: Int) {
|
private fun bindVideo(item: ContentItem, binding: VideoRowBinding, position: Int) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
ImageHelper.loadImage(item.thumbnail, thumbnail)
|
ImageHelper.loadImage(item.thumbnail, thumbnail)
|
||||||
|
|
||||||
thumbnailDuration.setFormattedDuration(item.duration, item.isShort, item.uploaded)
|
thumbnailDuration.setFormattedDuration(item.duration, item.isShort, item.uploaded)
|
||||||
videoTitle.text = item.title
|
videoTitle.text = item.title
|
||||||
videoInfo.text = TextUtils.formatViewsString(root.context, item.views, item.uploaded)
|
videoInfo.text = TextUtils.formatViewsString(root.context, item.views, item.uploaded)
|
||||||
|
|
||||||
|
channelContainer.isGone = item.uploaderAvatar.isNullOrEmpty()
|
||||||
channelName.text = item.uploaderName
|
channelName.text = item.uploaderName
|
||||||
ImageHelper.loadImage(item.uploaderAvatar, channelImage, true)
|
ImageHelper.loadImage(item.uploaderAvatar, channelImage, true)
|
||||||
|
|
||||||
@ -184,12 +187,10 @@ class SearchResultsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
root.setOnLongClickListener {
|
root.setOnLongClickListener {
|
||||||
val playlistId = item.url.toID()
|
|
||||||
val playlistName = item.name!!
|
|
||||||
val sheet = PlaylistOptionsBottomSheet()
|
val sheet = PlaylistOptionsBottomSheet()
|
||||||
sheet.arguments = bundleOf(
|
sheet.arguments = bundleOf(
|
||||||
IntentData.playlistId to playlistId,
|
IntentData.playlistId to item.url.toID(),
|
||||||
IntentData.playlistName to playlistName,
|
IntentData.playlistName to item.name.orEmpty(),
|
||||||
IntentData.playlistType to PlaylistType.PUBLIC
|
IntentData.playlistType to PlaylistType.PUBLIC
|
||||||
)
|
)
|
||||||
sheet.show(
|
sheet.show(
|
||||||
|
@ -3,26 +3,27 @@ package com.github.libretube.ui.fragments
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.paging.LoadState
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.MediaServiceRepository
|
import com.github.libretube.api.MediaServiceRepository
|
||||||
import com.github.libretube.api.obj.ChannelTab
|
import com.github.libretube.api.obj.ChannelTab
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.databinding.FragmentChannelContentBinding
|
import com.github.libretube.databinding.FragmentChannelContentBinding
|
||||||
import com.github.libretube.extensions.TAG
|
|
||||||
import com.github.libretube.extensions.ceilHalf
|
import com.github.libretube.extensions.ceilHalf
|
||||||
import com.github.libretube.extensions.parcelable
|
import com.github.libretube.extensions.parcelable
|
||||||
import com.github.libretube.extensions.parcelableArrayList
|
import com.github.libretube.extensions.parcelableArrayList
|
||||||
import com.github.libretube.ui.adapters.SearchChannelAdapter
|
import com.github.libretube.ui.adapters.SearchResultsAdapter
|
||||||
import com.github.libretube.ui.adapters.VideosAdapter
|
import com.github.libretube.ui.adapters.VideosAdapter
|
||||||
import com.github.libretube.ui.base.DynamicLayoutManagerFragment
|
import com.github.libretube.ui.base.DynamicLayoutManagerFragment
|
||||||
|
import com.github.libretube.ui.extensions.addOnBottomReachedListener
|
||||||
|
import com.github.libretube.ui.models.sources.ChannelTabPagingSource
|
||||||
import com.github.libretube.util.deArrow
|
import com.github.libretube.util.deArrow
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -31,12 +32,7 @@ import kotlinx.coroutines.withContext
|
|||||||
class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_channel_content) {
|
class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_channel_content) {
|
||||||
private var _binding: FragmentChannelContentBinding? = null
|
private var _binding: FragmentChannelContentBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
private var channelId: String? = null
|
|
||||||
private var searchChannelAdapter: SearchChannelAdapter? = null
|
|
||||||
private var channelAdapter: VideosAdapter? = null
|
|
||||||
private var recyclerViewState: Parcelable? = null
|
private var recyclerViewState: Parcelable? = null
|
||||||
private var nextPage: String? = null
|
|
||||||
private var isLoading: Boolean = false
|
|
||||||
|
|
||||||
override fun setLayoutManagers(gridItems: Int) {
|
override fun setLayoutManagers(gridItems: Int) {
|
||||||
binding.channelRecView.layoutManager = GridLayoutManager(
|
binding.channelRecView.layoutManager = GridLayoutManager(
|
||||||
@ -45,109 +41,26 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchChannelNextPage(nextPage: String): String? {
|
|
||||||
val response = withContext(Dispatchers.IO) {
|
|
||||||
MediaServiceRepository.instance.getChannelNextPage(channelId!!, nextPage).apply {
|
|
||||||
relatedStreams = relatedStreams.deArrow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
channelAdapter?.insertItems(response.relatedStreams)
|
|
||||||
return response.nextpage
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun fetchTabNextPage(nextPage: String, tab: ChannelTab): String? {
|
|
||||||
val newContent = withContext(Dispatchers.IO) {
|
|
||||||
MediaServiceRepository.instance.getChannelTab(tab.data, nextPage)
|
|
||||||
}.apply {
|
|
||||||
content = content.deArrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
searchChannelAdapter?.let {
|
|
||||||
it.submitList(it.currentList + newContent.content)
|
|
||||||
}
|
|
||||||
return newContent.nextpage
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
// manually restore the recyclerview state due to https://github.com/material-components/material-components-android/issues/3473
|
// manually restore the recyclerview state due to https://github.com/material-components/material-components-android/issues/3473
|
||||||
binding.channelRecView.layoutManager?.onRestoreInstanceState(recyclerViewState)
|
binding.channelRecView.layoutManager?.onRestoreInstanceState(recyclerViewState)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadChannelTab(tab: ChannelTab) = lifecycleScope.launch {
|
|
||||||
val response = try {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
MediaServiceRepository.instance.getChannelTab(tab.data)
|
|
||||||
}.apply {
|
|
||||||
content = content.deArrow()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
_binding?.progressBar?.isGone = true
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
nextPage = response.nextpage
|
|
||||||
|
|
||||||
searchChannelAdapter?.submitList(response.content)
|
|
||||||
val binding = _binding ?: return@launch
|
|
||||||
binding.progressBar.isGone = true
|
|
||||||
|
|
||||||
isLoading = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadNextPage(isVideo: Boolean, tab: ChannelTab) {
|
|
||||||
if (isLoading) return
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
try {
|
|
||||||
isLoading = true
|
|
||||||
nextPage = if (isVideo) {
|
|
||||||
fetchChannelNextPage(nextPage ?: return@launch)
|
|
||||||
} else {
|
|
||||||
fetchTabNextPage(nextPage ?: return@launch, tab)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG(), e.toString())
|
|
||||||
}
|
|
||||||
isLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
_binding = FragmentChannelContentBinding.bind(view)
|
_binding = FragmentChannelContentBinding.bind(view)
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val arguments = requireArguments()
|
val arguments = requireArguments()
|
||||||
|
val channelId = arguments.getString(IntentData.channelId)!!
|
||||||
|
|
||||||
val tabData = arguments.parcelable<ChannelTab>(IntentData.tabData)
|
val tabData = arguments.parcelable<ChannelTab>(IntentData.tabData)
|
||||||
channelId = arguments.getString(IntentData.channelId)
|
|
||||||
nextPage = arguments.getString(IntentData.nextPage)
|
|
||||||
|
|
||||||
searchChannelAdapter = SearchChannelAdapter()
|
|
||||||
binding.channelRecView.adapter = searchChannelAdapter
|
|
||||||
|
|
||||||
binding.channelRecView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
|
||||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
|
||||||
super.onScrollStateChanged(recyclerView, newState)
|
|
||||||
recyclerViewState = binding.channelRecView.layoutManager?.onSaveInstanceState()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
|
||||||
|
|
||||||
if (_binding == null || isLoading) return
|
|
||||||
|
|
||||||
val visibleItemCount = recyclerView.layoutManager!!.childCount
|
|
||||||
val totalItemCount = recyclerView.layoutManager!!.getItemCount()
|
|
||||||
val firstVisibleItemPosition =
|
|
||||||
(recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
|
||||||
|
|
||||||
if (firstVisibleItemPosition + visibleItemCount >= totalItemCount) {
|
|
||||||
loadNextPage(tabData?.data!!.isEmpty(), tabData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (tabData?.data.isNullOrEmpty()) {
|
if (tabData?.data.isNullOrEmpty()) {
|
||||||
channelAdapter = VideosAdapter(
|
var nextPage = arguments.getString(IntentData.nextPage)
|
||||||
|
var isLoading = false
|
||||||
|
|
||||||
|
val channelAdapter = VideosAdapter(
|
||||||
forceMode = VideosAdapter.Companion.LayoutMode.CHANNEL_ROW
|
forceMode = VideosAdapter.Companion.LayoutMode.CHANNEL_ROW
|
||||||
).also {
|
).also {
|
||||||
it.submitList(arguments.parcelableArrayList<StreamItem>(IntentData.videoList)!!)
|
it.submitList(arguments.parcelableArrayList<StreamItem>(IntentData.videoList)!!)
|
||||||
@ -155,8 +68,52 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch
|
|||||||
binding.channelRecView.adapter = channelAdapter
|
binding.channelRecView.adapter = channelAdapter
|
||||||
binding.progressBar.isGone = true
|
binding.progressBar.isGone = true
|
||||||
|
|
||||||
|
binding.channelRecView.addOnBottomReachedListener {
|
||||||
|
if (isLoading || nextPage == null) return@addOnBottomReachedListener
|
||||||
|
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val resp = try {
|
||||||
|
MediaServiceRepository.instance.getChannelNextPage(channelId, nextPage!!).apply {
|
||||||
|
relatedStreams = relatedStreams.deArrow()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return@launch
|
||||||
|
} finally {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage = resp.nextpage
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
channelAdapter.insertItems(resp.relatedStreams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
loadChannelTab(tabData ?: return)
|
val searchChannelAdapter = SearchResultsAdapter()
|
||||||
|
binding.channelRecView.adapter = searchChannelAdapter
|
||||||
|
|
||||||
|
val pagingFlow = Pager(
|
||||||
|
PagingConfig(pageSize = 20, enablePlaceholders = false),
|
||||||
|
pagingSourceFactory = { ChannelTabPagingSource(tabData!!) }
|
||||||
|
).flow
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
launch {
|
||||||
|
pagingFlow.collect {
|
||||||
|
searchChannelAdapter.submitData(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
searchChannelAdapter.loadStateFlow.collect {
|
||||||
|
if (it.refresh is LoadState.NotLoading) {
|
||||||
|
binding.progressBar.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.github.libretube.ui.models.sources
|
||||||
|
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import com.github.libretube.api.MediaServiceRepository
|
||||||
|
import com.github.libretube.api.obj.ChannelTab
|
||||||
|
import com.github.libretube.api.obj.ContentItem
|
||||||
|
import com.github.libretube.util.deArrow
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class ChannelTabPagingSource(
|
||||||
|
private val tab: ChannelTab
|
||||||
|
): PagingSource<String, ContentItem>() {
|
||||||
|
override fun getRefreshKey(state: PagingState<String, ContentItem>) = null
|
||||||
|
|
||||||
|
override suspend fun load(params: LoadParams<String>): LoadResult<String, ContentItem> {
|
||||||
|
return try {
|
||||||
|
val resp = withContext(Dispatchers.IO) {
|
||||||
|
MediaServiceRepository.instance.getChannelTab(tab.data, params.key).apply {
|
||||||
|
content = content.deArrow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoadResult.Page(resp.content, null, resp.nextpage)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LoadResult.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user