From 2ac5f1b0a2e687318b262251443e156a28987fe6 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Wed, 29 Nov 2023 16:34:33 +0100 Subject: [PATCH] refactor: simplify channel page and tabs logic --- .../libretube/ui/fragments/ChannelFragment.kt | 384 +++++++++--------- 1 file changed, 185 insertions(+), 199 deletions(-) diff --git a/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt index e99e06111..f1b0a1812 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt @@ -31,11 +31,11 @@ import com.github.libretube.ui.adapters.VideosAdapter import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.extensions.setupSubscriptionButton import com.github.libretube.util.deArrow -import java.io.IOException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import retrofit2.HttpException +import java.io.IOException class ChannelFragment : Fragment() { private var _binding: FragmentChannelBinding? = null @@ -43,19 +43,19 @@ class ChannelFragment : Fragment() { private var channelId: String? = null private var channelName: String? = null - private var nextPage: String? = null private var channelAdapter: VideosAdapter? = null private var isLoading = true private var isSubscribed: Boolean? = false - private var onScrollEnd: () -> Unit = {} - - private val possibleTabs = listOf( - ChannelTabs.Channels, - ChannelTabs.Playlists, + private val possibleTabs = arrayOf( + ChannelTabs.Shorts, ChannelTabs.Livestreams, - ChannelTabs.Shorts + ChannelTabs.Playlists, + ChannelTabs.Channels ) + private var channelTabs: List = emptyList() + private var nextPages = Array(5) { null } + private var searchAdapter: SearchAdapter? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -81,26 +81,19 @@ class ChannelFragment : Fragment() { binding.channelRecView.layoutManager = LinearLayoutManager(context) - val refreshChannel = { - binding.channelRefresh.isRefreshing = true + binding.channelRefresh.setOnRefreshListener { fetchChannel() } - refreshChannel() - - binding.channelRefresh.setOnRefreshListener { - refreshChannel() - } - binding.channelScrollView.viewTreeObserver.addOnScrollChangedListener { - if (_binding?.channelScrollView?.canScrollVertically(1) == false) { - try { - onScrollEnd() - } catch (e: Exception) { - Log.e("tabs failed", e.toString()) - } - } + val binding = _binding ?: return@addOnScrollChangedListener + + if (binding.channelScrollView.canScrollVertically(1) || isLoading) return@addOnScrollChangedListener + + loadNextPage() } + + fetchChannel() } override fun onDestroyView() { @@ -108,112 +101,139 @@ class ChannelFragment : Fragment() { _binding = null } - private fun fetchChannel() { - lifecycleScope.launch { - val response = try { - withContext(Dispatchers.IO) { - if (channelId != null) { - RetrofitInstance.api.getChannel(channelId!!) - } else { - RetrofitInstance.api.getChannelByName(channelName!!) - }.apply { - relatedStreams = relatedStreams.deArrow() - } + private fun loadNextPage() = lifecycleScope.launch { + val binding = _binding ?: return@launch + + binding.channelRefresh.isRefreshing = true + isLoading = true + + try { + if (binding.tabChips.checkedChipId == binding.videos.id) { + fetchChannelNextPage(nextPages[0] ?: return@launch)?.let { + nextPages[0] = it } - } catch (e: IOException) { - _binding?.channelRefresh?.isRefreshing = false - Log.e(TAG(), "IOException, you might not have internet connection") - return@launch - } catch (e: HttpException) { - _binding?.channelRefresh?.isRefreshing = false - Log.e(TAG(), "HttpException, unexpected response") - return@launch - } - val binding = _binding ?: return@launch - - // needed if the channel gets loaded by the ID - channelId = response.id - channelName = response.name - val shareData = ShareData(currentChannel = response.name) - - onScrollEnd = { - fetchChannelNextPage() - } - - val channelId = channelId ?: return@launch - // fetch and update the subscription status - isSubscribed = SubscriptionHelper.isSubscribed(channelId) ?: false - - binding.channelSubscribe.setupSubscriptionButton( - channelId, - channelName, - binding.notificationBell - ) - - binding.channelShare.setOnClickListener { - val bundle = bundleOf( - IntentData.id to channelId.toID(), - IntentData.shareObjectType to ShareObjectType.CHANNEL, - IntentData.shareData to shareData - ) - val newShareDialog = ShareDialog() - newShareDialog.arguments = bundle - newShareDialog.show(childFragmentManager, ShareDialog::class.java.name) - } - - nextPage = response.nextpage - isLoading = false - binding.channelRefresh.isRefreshing = false - - binding.channelScrollView.isVisible = true - binding.channelName.text = response.name - if (response.verified) { - binding.channelName.setCompoundDrawablesWithIntrinsicBounds( - 0, - 0, - R.drawable.ic_verified, - 0 - ) - } - binding.channelSubs.text = resources.getString( - R.string.subscribers, - response.subscriberCount.formatShort() - ) - if (response.description.orEmpty().isBlank()) { - binding.channelDescription.isGone = true } else { - binding.channelDescription.text = response.description.orEmpty().trim() + val currentTabIndex = binding.tabChips.children.indexOfFirst { + it.id == binding.tabChips.checkedChipId + } + val channelTab = channelTabs.first { tab -> + tab.name == possibleTabs[currentTabIndex - 1].identifierName + } + val nextPage = nextPages[currentTabIndex] ?: return@launch + fetchTabNextPage(nextPage, channelTab)?.let { + nextPages[currentTabIndex] = it + } } - - ImageHelper.loadImage(response.bannerUrl, binding.channelBanner) - ImageHelper.loadImage(response.avatarUrl, binding.channelImage) - - binding.channelImage.setOnClickListener { - NavigationHelper.openImagePreview( - requireContext(), - response.avatarUrl ?: return@setOnClickListener - ) - } - - binding.channelBanner.setOnClickListener { - NavigationHelper.openImagePreview( - requireContext(), - response.bannerUrl ?: return@setOnClickListener - ) - } - - // recyclerview of the videos by the channel - channelAdapter = VideosAdapter( - response.relatedStreams.toMutableList(), - forceMode = VideosAdapter.Companion.ForceMode.CHANNEL - ) - binding.channelRecView.adapter = channelAdapter - - setupTabs(response.tabs) + } catch (e: Exception) { + Log.e("error fetching tabs", e.toString()) } + }.invokeOnCompletion { + _binding?.channelRefresh?.isRefreshing = false + isLoading = false + } + + private fun fetchChannel() = lifecycleScope.launch { + isLoading = true + binding.channelRefresh.isRefreshing = true + + val response = try { + withContext(Dispatchers.IO) { + if (channelId != null) { + RetrofitInstance.api.getChannel(channelId!!) + } else { + RetrofitInstance.api.getChannelByName(channelName!!) + }.apply { + relatedStreams = relatedStreams.deArrow() + } + } + } catch (e: IOException) { + Log.e(TAG(), "IOException, you might not have internet connection") + return@launch + } catch (e: HttpException) { + Log.e(TAG(), "HttpException, unexpected response") + return@launch + } finally { + _binding?.channelRefresh?.isRefreshing = false + isLoading = false + } + val binding = _binding ?: return@launch + + // needed if the channel gets loaded by the ID + channelId = response.id + channelName = response.name + val shareData = ShareData(currentChannel = response.name) + + val channelId = channelId ?: return@launch + // fetch and update the subscription status + isSubscribed = SubscriptionHelper.isSubscribed(channelId) ?: false + + binding.channelSubscribe.setupSubscriptionButton( + channelId, + channelName, + binding.notificationBell + ) + + binding.channelShare.setOnClickListener { + val bundle = bundleOf( + IntentData.id to channelId.toID(), + IntentData.shareObjectType to ShareObjectType.CHANNEL, + IntentData.shareData to shareData + ) + val newShareDialog = ShareDialog() + newShareDialog.arguments = bundle + newShareDialog.show(childFragmentManager, ShareDialog::class.java.name) + } + + nextPages[0] = response.nextpage + isLoading = false + binding.channelRefresh.isRefreshing = false + + binding.channelScrollView.isVisible = true + binding.channelName.text = response.name + if (response.verified) { + binding.channelName + .setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_verified, 0) + } + binding.channelSubs.text = resources.getString( + R.string.subscribers, + response.subscriberCount.formatShort() + ) + if (response.description.orEmpty().isBlank()) { + binding.channelDescription.isGone = true + } else { + binding.channelDescription.text = response.description.orEmpty().trim() + } + + ImageHelper.loadImage(response.bannerUrl, binding.channelBanner) + ImageHelper.loadImage(response.avatarUrl, binding.channelImage) + + binding.channelImage.setOnClickListener { + NavigationHelper.openImagePreview( + requireContext(), + response.avatarUrl ?: return@setOnClickListener + ) + } + + binding.channelBanner.setOnClickListener { + NavigationHelper.openImagePreview( + requireContext(), + response.bannerUrl ?: return@setOnClickListener + ) + } + + // recyclerview of the videos by the channel + channelAdapter = VideosAdapter( + response.relatedStreams.toMutableList(), + forceMode = VideosAdapter.Companion.ForceMode.CHANNEL + ) + binding.channelRecView.adapter = channelAdapter + + setupTabs(response.tabs) } private fun setupTabs(tabs: List) { + this.channelTabs = tabs + binding.tabChips.children.forEach { chip -> val resourceTab = possibleTabs.firstOrNull { it.chipId == chip.id } resourceTab?.let { resTab -> @@ -225,15 +245,12 @@ class ChannelFragment : Fragment() { when (binding.tabChips.checkedChipId) { binding.videos.id -> { binding.channelRecView.adapter = channelAdapter - onScrollEnd = { - fetchChannelNextPage() - } } else -> { possibleTabs.first { binding.tabChips.checkedChipId == it.chipId }.let { val tab = tabs.first { tab -> tab.name == it.identifierName } - loadTab(tab) + loadChannelTab(tab) } } } @@ -242,89 +259,58 @@ class ChannelFragment : Fragment() { // Load selected chip content if it's not videos tab. possibleTabs.firstOrNull { binding.tabChips.checkedChipId == it.chipId }?.let { val tab = tabs.first { tab -> tab.name == it.identifierName } - loadTab(tab) + loadChannelTab(tab) } } - private fun loadTab(tab: ChannelTab) { - lifecycleScope.launch { - val response = try { - withContext(Dispatchers.IO) { - RetrofitInstance.api.getChannelTab(tab.data) - }.apply { - content = content.deArrow() - } - } catch (e: Exception) { - return@launch - } - val binding = _binding ?: return@launch - - val adapter = SearchAdapter(true) - binding.channelRecView.adapter = adapter - adapter.submitList(response.content) - - var tabNextPage = response.nextpage - onScrollEnd = { - tabNextPage?.let { - fetchTabNextPage(it, tab, adapter) { nextPage -> - tabNextPage = nextPage - } - } - } - } - } - - private fun fetchChannelNextPage() { - if (nextPage == null || isLoading) return - isLoading = true + private fun loadChannelTab(tab: ChannelTab) = lifecycleScope.launch { binding.channelRefresh.isRefreshing = true + isLoading = true - lifecycleScope.launch { - val response = try { - withContext(Dispatchers.IO) { - RetrofitInstance.api.getChannelNextPage(channelId!!, nextPage!!).apply { - relatedStreams = relatedStreams.deArrow() - } - } - } catch (e: IOException) { - _binding?.channelRefresh?.isRefreshing = false - Log.e(TAG(), "IOException, you might not have internet connection") - return@launch - } catch (e: HttpException) { - _binding?.channelRefresh?.isRefreshing = false - Log.e(TAG(), "HttpException, unexpected response," + e.response()) - return@launch + val response = try { + withContext(Dispatchers.IO) { + RetrofitInstance.api.getChannelTab(tab.data) + }.apply { + content = content.deArrow() } - val binding = _binding ?: return@launch - - nextPage = response.nextpage - channelAdapter?.insertItems(response.relatedStreams) - isLoading = false - binding.channelRefresh.isRefreshing = false + } catch (e: Exception) { + return@launch } + nextPages[channelTabs.indexOf(tab) + 1] = response.nextpage + + val binding = _binding ?: return@launch + + searchAdapter = SearchAdapter(true) + binding.channelRecView.adapter = searchAdapter + searchAdapter?.submitList(response.content) + + binding.channelRefresh.isRefreshing = false + isLoading = false } - private fun fetchTabNextPage( - nextPage: String, - tab: ChannelTab, - adapter: SearchAdapter, - onNewNextPage: (String?) -> Unit - ) { - lifecycleScope.launch { - val newContent = try { - withContext(Dispatchers.IO) { - RetrofitInstance.api.getChannelTab(tab.data, nextPage) - }.apply { - content = content.deArrow() - } - } catch (e: Exception) { - Log.e(TAG(), "Exception: $e") - null - } - onNewNextPage(newContent?.nextpage) - newContent?.content?.let { - adapter.submitList(adapter.currentList + it) + private suspend fun fetchChannelNextPage(nextPage: String): String? { + val response = withContext(Dispatchers.IO) { + RetrofitInstance.api.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) { + RetrofitInstance.api.getChannelTab(tab.data, nextPage) + }.apply { + content = content.deArrow() + } + + searchAdapter?.let { + it.submitList(it.currentList + newContent.content) + } + + return newContent.nextpage } }