mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-14 14:20:30 +05:30
Merge pull request #5250 from Bnyro/master
refactor: simplify channel page and tabs logic
This commit is contained in:
commit
3ac85a75ea
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user