Convert SearchAdapter to a ListAdapter.

This commit is contained in:
Isira Seneviratne 2023-02-05 06:01:12 +05:30
parent c05734d0fe
commit 3b6ccdb91d
3 changed files with 53 additions and 59 deletions

View File

@ -4,7 +4,8 @@ import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.obj.ContentItem import com.github.libretube.api.obj.ContentItem
import com.github.libretube.databinding.ChannelRowBinding import com.github.libretube.databinding.ChannelRowBinding
@ -25,21 +26,7 @@ import com.github.libretube.ui.sheets.VideoOptionsBottomSheet
import com.github.libretube.ui.viewholders.SearchViewHolder import com.github.libretube.ui.viewholders.SearchViewHolder
import com.github.libretube.util.TextUtils import com.github.libretube.util.TextUtils
class SearchAdapter( class SearchAdapter : ListAdapter<ContentItem, SearchViewHolder>(SearchCallback) {
private val searchItems: MutableList<ContentItem>
) :
RecyclerView.Adapter<SearchViewHolder>() {
fun updateItems(newItems: List<ContentItem>) {
val searchItemsSize = searchItems.size
searchItems.addAll(newItems)
notifyItemRangeInserted(searchItemsSize, newItems.size)
}
override fun getItemCount(): Int {
return searchItems.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
val layoutInflater = LayoutInflater.from(parent.context) val layoutInflater = LayoutInflater.from(parent.context)
@ -58,7 +45,7 @@ class SearchAdapter(
} }
override fun onBindViewHolder(holder: SearchViewHolder, position: Int) { override fun onBindViewHolder(holder: SearchViewHolder, position: Int) {
val searchItem = searchItems[position] val searchItem = currentList[position]
val videoRowBinding = holder.videoRowBinding val videoRowBinding = holder.videoRowBinding
val channelRowBinding = holder.channelRowBinding val channelRowBinding = holder.channelRowBinding
@ -68,11 +55,13 @@ class SearchAdapter(
bindWatch(searchItem, videoRowBinding) bindWatch(searchItem, videoRowBinding)
} else if (channelRowBinding != null) { } else if (channelRowBinding != null) {
bindChannel(searchItem, channelRowBinding) bindChannel(searchItem, channelRowBinding)
} else if (playlistRowBinding != null) bindPlaylist(searchItem, playlistRowBinding) } else if (playlistRowBinding != null) {
bindPlaylist(searchItem, playlistRowBinding)
}
} }
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when (searchItems[position].type) { return when (currentList[position].type) {
"stream" -> 0 "stream" -> 0
"channel" -> 1 "channel" -> 1
"playlist" -> 2 "playlist" -> 2
@ -116,10 +105,7 @@ class SearchAdapter(
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun bindChannel( private fun bindChannel(item: ContentItem, binding: ChannelRowBinding) {
item: ContentItem,
binding: ChannelRowBinding
) {
binding.apply { binding.apply {
ImageHelper.loadImage(item.thumbnail, searchChannelImage) ImageHelper.loadImage(item.thumbnail, searchChannelImage)
searchChannelName.text = item.name searchChannelName.text = item.name
@ -144,10 +130,7 @@ class SearchAdapter(
} }
} }
private fun bindPlaylist( private fun bindPlaylist(item: ContentItem, binding: PlaylistsRowBinding) {
item: ContentItem,
binding: PlaylistsRowBinding
) {
binding.apply { binding.apply {
ImageHelper.loadImage(item.thumbnail, playlistThumbnail) ImageHelper.loadImage(item.thumbnail, playlistThumbnail)
if (item.videos != -1L) videoCount.text = item.videos.toString() if (item.videos != -1L) videoCount.text = item.videos.toString()
@ -169,4 +152,14 @@ class SearchAdapter(
} }
} }
} }
private object SearchCallback : DiffUtil.ItemCallback<ContentItem>() {
override fun areItemsTheSame(oldItem: ContentItem, newItem: ContentItem): Boolean {
return oldItem.url == newItem.url
}
override fun areContentsTheSame(oldItem: ContentItem, newItem: ContentItem): Boolean {
return true
}
}
} }

View File

@ -28,9 +28,9 @@ import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.ui.extensions.setupSubscriptionButton import com.github.libretube.ui.extensions.setupSubscriptionButton
import java.io.IOException import java.io.IOException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import retrofit2.HttpException import retrofit2.HttpException
class ChannelFragment : BaseFragment() { class ChannelFragment : BaseFragment() {
@ -45,8 +45,6 @@ class ChannelFragment : BaseFragment() {
private var onScrollEnd: () -> Unit = {} private var onScrollEnd: () -> Unit = {}
private val scope = CoroutineScope(Dispatchers.IO)
val possibleTabs = listOf( val possibleTabs = listOf(
ChannelTabs.Channels, ChannelTabs.Channels,
ChannelTabs.Playlists, ChannelTabs.Playlists,
@ -227,18 +225,18 @@ class ChannelFragment : BaseFragment() {
} }
private fun loadTab(tab: ChannelTab) { private fun loadTab(tab: ChannelTab) {
scope.launch { lifecycleScope.launch {
val response = try { val response = try {
RetrofitInstance.api.getChannelTab(tab.data) withContext(Dispatchers.IO) {
RetrofitInstance.api.getChannelTab(tab.data)
}
} catch (e: Exception) { } catch (e: Exception) {
return@launch return@launch
} }
val adapter = SearchAdapter(response.content.toMutableList()) val adapter = SearchAdapter()
binding.channelRecView.adapter = adapter
runOnUiThread { adapter.submitList(response.content)
binding.channelRecView.adapter = adapter
}
var tabNextPage = response.nextpage var tabNextPage = response.nextpage
onScrollEnd = { onScrollEnd = {
@ -284,18 +282,18 @@ class ChannelFragment : BaseFragment() {
adapter: SearchAdapter, adapter: SearchAdapter,
onNewNextPage: (String?) -> Unit onNewNextPage: (String?) -> Unit
) { ) {
scope.launch { lifecycleScope.launch {
val newContent = try { val newContent = try {
RetrofitInstance.api.getChannelTab(tab.data, nextPage) withContext(Dispatchers.IO) {
RetrofitInstance.api.getChannelTab(tab.data, nextPage)
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG(), "Exception: $e") Log.e(TAG(), "Exception: $e")
null null
} }
onNewNextPage.invoke(newContent?.nextpage) onNewNextPage(newContent?.nextpage)
runOnUiThread { newContent?.content?.let {
newContent?.content?.let { adapter.submitList(adapter.currentList + it)
adapter.updateItems(it)
}
} }
} }
} }

View File

@ -19,6 +19,8 @@ import com.github.libretube.extensions.hideKeyboard
import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.ui.adapters.SearchAdapter import com.github.libretube.ui.adapters.SearchAdapter
import com.github.libretube.ui.base.BaseFragment import com.github.libretube.ui.base.BaseFragment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.IOException import java.io.IOException
import retrofit2.HttpException import retrofit2.HttpException
@ -85,7 +87,9 @@ class SearchResultFragment : BaseFragment() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
view?.let { context?.hideKeyboard(it) } view?.let { context?.hideKeyboard(it) }
val response = try { val response = try {
RetrofitInstance.api.getSearchResults(query, apiSearchFilter) withContext(Dispatchers.IO) {
RetrofitInstance.api.getSearchResults(query, apiSearchFilter)
}
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG(), "IOException, you might not have internet connection $e") Log.e(TAG(), "IOException, you might not have internet connection $e")
@ -94,11 +98,10 @@ class SearchResultFragment : BaseFragment() {
Log.e(TAG(), "HttpException, unexpected response") Log.e(TAG(), "HttpException, unexpected response")
return@launchWhenCreated return@launchWhenCreated
} }
runOnUiThread { searchAdapter = SearchAdapter()
searchAdapter = SearchAdapter(response.items.toMutableList()) binding.searchRecycler.adapter = searchAdapter
binding.searchRecycler.adapter = searchAdapter searchAdapter.submitList(response.items)
binding.noSearchResult.isVisible = response.items.isEmpty() binding.noSearchResult.isVisible = response.items.isEmpty()
}
nextPage = response.nextpage nextPage = response.nextpage
} }
} }
@ -106,11 +109,13 @@ class SearchResultFragment : BaseFragment() {
private fun fetchNextSearchItems() { private fun fetchNextSearchItems() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
val response = try { val response = try {
RetrofitInstance.api.getSearchResultsNextPage( withContext(Dispatchers.IO) {
query, RetrofitInstance.api.getSearchResultsNextPage(
apiSearchFilter, query,
nextPage!! apiSearchFilter,
) nextPage!!
)
}
} catch (e: IOException) { } catch (e: IOException) {
println(e) println(e)
Log.e(TAG(), "IOException, you might not have internet connection") Log.e(TAG(), "IOException, you might not have internet connection")
@ -120,10 +125,8 @@ class SearchResultFragment : BaseFragment() {
return@launchWhenCreated return@launchWhenCreated
} }
nextPage = response.nextpage!! nextPage = response.nextpage!!
kotlin.runCatching { if (response.items.isNotEmpty()) {
if (response.items.isNotEmpty()) { searchAdapter.submitList(searchAdapter.currentList + response.items)
searchAdapter.updateItems(response.items)
}
} }
} }
} }