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.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<ChannelTab> = emptyList()
private var nextPages = Array<String?>(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<ChannelTab>) {
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
}
}