Merge pull request #5250 from Bnyro/master

refactor: simplify channel page and tabs logic
This commit is contained in:
Bnyro 2023-11-29 16:34:50 +01:00 committed by GitHub
commit 3ac85a75ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -31,11 +31,11 @@ import com.github.libretube.ui.adapters.VideosAdapter
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 com.github.libretube.util.deArrow import com.github.libretube.util.deArrow
import java.io.IOException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException
class ChannelFragment : Fragment() { class ChannelFragment : Fragment() {
private var _binding: FragmentChannelBinding? = null private var _binding: FragmentChannelBinding? = null
@ -43,19 +43,19 @@ class ChannelFragment : Fragment() {
private var channelId: String? = null private var channelId: String? = null
private var channelName: String? = null private var channelName: String? = null
private var nextPage: String? = null
private var channelAdapter: VideosAdapter? = null private var channelAdapter: VideosAdapter? = null
private var isLoading = true private var isLoading = true
private var isSubscribed: Boolean? = false private var isSubscribed: Boolean? = false
private var onScrollEnd: () -> Unit = {} private val possibleTabs = arrayOf(
ChannelTabs.Shorts,
private val possibleTabs = listOf(
ChannelTabs.Channels,
ChannelTabs.Playlists,
ChannelTabs.Livestreams, ChannelTabs.Livestreams,
ChannelTabs.Shorts ChannelTabs.Playlists,
ChannelTabs.Channels
) )
private var channelTabs: List<ChannelTab> = emptyList()
private var nextPages = Array<String?>(5) { null }
private var searchAdapter: SearchAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -81,26 +81,19 @@ class ChannelFragment : Fragment() {
binding.channelRecView.layoutManager = LinearLayoutManager(context) binding.channelRecView.layoutManager = LinearLayoutManager(context)
val refreshChannel = { binding.channelRefresh.setOnRefreshListener {
binding.channelRefresh.isRefreshing = true
fetchChannel() fetchChannel()
} }
refreshChannel()
binding.channelRefresh.setOnRefreshListener {
refreshChannel()
}
binding.channelScrollView.viewTreeObserver.addOnScrollChangedListener { binding.channelScrollView.viewTreeObserver.addOnScrollChangedListener {
if (_binding?.channelScrollView?.canScrollVertically(1) == false) { val binding = _binding ?: return@addOnScrollChangedListener
try {
onScrollEnd() if (binding.channelScrollView.canScrollVertically(1) || isLoading) return@addOnScrollChangedListener
} catch (e: Exception) {
Log.e("tabs failed", e.toString()) loadNextPage()
}
}
} }
fetchChannel()
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -108,112 +101,139 @@ class ChannelFragment : Fragment() {
_binding = null _binding = null
} }
private fun fetchChannel() { private fun loadNextPage() = lifecycleScope.launch {
lifecycleScope.launch { val binding = _binding ?: return@launch
val response = try {
withContext(Dispatchers.IO) { binding.channelRefresh.isRefreshing = true
if (channelId != null) { isLoading = true
RetrofitInstance.api.getChannel(channelId!!)
} else { try {
RetrofitInstance.api.getChannelByName(channelName!!) if (binding.tabChips.checkedChipId == binding.videos.id) {
}.apply { fetchChannelNextPage(nextPages[0] ?: return@launch)?.let {
relatedStreams = relatedStreams.deArrow() 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 { } 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
}
} }
} catch (e: Exception) {
ImageHelper.loadImage(response.bannerUrl, binding.channelBanner) Log.e("error fetching tabs", e.toString())
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)
} }
}.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<ChannelTab>) { private fun setupTabs(tabs: List<ChannelTab>) {
this.channelTabs = tabs
binding.tabChips.children.forEach { chip -> binding.tabChips.children.forEach { chip ->
val resourceTab = possibleTabs.firstOrNull { it.chipId == chip.id } val resourceTab = possibleTabs.firstOrNull { it.chipId == chip.id }
resourceTab?.let { resTab -> resourceTab?.let { resTab ->
@ -225,15 +245,12 @@ class ChannelFragment : Fragment() {
when (binding.tabChips.checkedChipId) { when (binding.tabChips.checkedChipId) {
binding.videos.id -> { binding.videos.id -> {
binding.channelRecView.adapter = channelAdapter binding.channelRecView.adapter = channelAdapter
onScrollEnd = {
fetchChannelNextPage()
}
} }
else -> { else -> {
possibleTabs.first { binding.tabChips.checkedChipId == it.chipId }.let { possibleTabs.first { binding.tabChips.checkedChipId == it.chipId }.let {
val tab = tabs.first { tab -> tab.name == it.identifierName } 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. // Load selected chip content if it's not videos tab.
possibleTabs.firstOrNull { binding.tabChips.checkedChipId == it.chipId }?.let { possibleTabs.firstOrNull { binding.tabChips.checkedChipId == it.chipId }?.let {
val tab = tabs.first { tab -> tab.name == it.identifierName } val tab = tabs.first { tab -> tab.name == it.identifierName }
loadTab(tab) loadChannelTab(tab)
} }
} }
private fun loadTab(tab: ChannelTab) { private fun loadChannelTab(tab: ChannelTab) = lifecycleScope.launch {
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
binding.channelRefresh.isRefreshing = true binding.channelRefresh.isRefreshing = true
isLoading = true
lifecycleScope.launch { val response = try {
val response = try { withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { RetrofitInstance.api.getChannelTab(tab.data)
RetrofitInstance.api.getChannelNextPage(channelId!!, nextPage!!).apply { }.apply {
relatedStreams = relatedStreams.deArrow() content = content.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 binding = _binding ?: return@launch } catch (e: Exception) {
return@launch
nextPage = response.nextpage
channelAdapter?.insertItems(response.relatedStreams)
isLoading = false
binding.channelRefresh.isRefreshing = false
} }
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( private suspend fun fetchChannelNextPage(nextPage: String): String? {
nextPage: String, val response = withContext(Dispatchers.IO) {
tab: ChannelTab, RetrofitInstance.api.getChannelNextPage(channelId!!, nextPage).apply {
adapter: SearchAdapter, relatedStreams = relatedStreams.deArrow()
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)
} }
} }
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
} }
} }