From 36e2da52fb4a4a7a890c81566db817ea33016683 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 29 Oct 2022 11:20:34 +0200 Subject: [PATCH 1/3] Channel Tabs backend --- .../main/java/com/github/libretube/api/PipedApi.kt | 7 +++++++ .../java/com/github/libretube/api/obj/Channel.kt | 3 ++- .../java/com/github/libretube/api/obj/ChannelTab.kt | 9 +++++++++ .../github/libretube/api/obj/ChannelTabResponse.kt | 6 ++++++ .../api/obj/{SearchItem.kt => ContentItem.kt} | 2 +- .../com/github/libretube/api/obj/SearchResult.kt | 2 +- .../github/libretube/ui/adapters/SearchAdapter.kt | 12 ++++++------ .../github/libretube/ui/fragments/ChannelFragment.kt | 8 ++++++++ 8 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/github/libretube/api/obj/ChannelTab.kt create mode 100644 app/src/main/java/com/github/libretube/api/obj/ChannelTabResponse.kt rename app/src/main/java/com/github/libretube/api/obj/{SearchItem.kt => ContentItem.kt} (97%) diff --git a/app/src/main/java/com/github/libretube/api/PipedApi.kt b/app/src/main/java/com/github/libretube/api/PipedApi.kt index 061ea5691..f182fcae6 100644 --- a/app/src/main/java/com/github/libretube/api/PipedApi.kt +++ b/app/src/main/java/com/github/libretube/api/PipedApi.kt @@ -1,6 +1,7 @@ package com.github.libretube.api import com.github.libretube.api.obj.Channel +import com.github.libretube.api.obj.ChannelTabResponse import com.github.libretube.api.obj.CommentsPage import com.github.libretube.api.obj.DeleteUserRequest import com.github.libretube.api.obj.Login @@ -63,6 +64,12 @@ interface PipedApi { @GET("channel/{channelId}") suspend fun getChannel(@Path("channelId") channelId: String): Channel + @GET("channels/tabs") + suspend fun getChannelTab( + @Query("data") data: String, + @Query("nextpage") nextPage: String? = null + ): ChannelTabResponse + @GET("user/{name}") suspend fun getChannelByName(@Path("name") channelName: String): Channel diff --git a/app/src/main/java/com/github/libretube/api/obj/Channel.kt b/app/src/main/java/com/github/libretube/api/obj/Channel.kt index d255187dd..e1b261df4 100644 --- a/app/src/main/java/com/github/libretube/api/obj/Channel.kt +++ b/app/src/main/java/com/github/libretube/api/obj/Channel.kt @@ -12,5 +12,6 @@ data class Channel( var nextpage: String? = null, var subscriberCount: Long = 0, var verified: Boolean = false, - var relatedStreams: List? = null + var relatedStreams: List? = listOf(), + var tabs: List? = listOf() ) diff --git a/app/src/main/java/com/github/libretube/api/obj/ChannelTab.kt b/app/src/main/java/com/github/libretube/api/obj/ChannelTab.kt new file mode 100644 index 000000000..c7e2d63de --- /dev/null +++ b/app/src/main/java/com/github/libretube/api/obj/ChannelTab.kt @@ -0,0 +1,9 @@ +package com.github.libretube.api.obj + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties + +@JsonIgnoreProperties(ignoreUnknown = true) +data class ChannelTab( + val name: String? = null, + val data: String? = null +) diff --git a/app/src/main/java/com/github/libretube/api/obj/ChannelTabResponse.kt b/app/src/main/java/com/github/libretube/api/obj/ChannelTabResponse.kt new file mode 100644 index 000000000..6c6fd7bae --- /dev/null +++ b/app/src/main/java/com/github/libretube/api/obj/ChannelTabResponse.kt @@ -0,0 +1,6 @@ +package com.github.libretube.api.obj + +data class ChannelTabResponse( + val content: List = listOf(), + val nextpage: String? = null +) diff --git a/app/src/main/java/com/github/libretube/api/obj/SearchItem.kt b/app/src/main/java/com/github/libretube/api/obj/ContentItem.kt similarity index 97% rename from app/src/main/java/com/github/libretube/api/obj/SearchItem.kt rename to app/src/main/java/com/github/libretube/api/obj/ContentItem.kt index f55604d8b..af7181e5c 100644 --- a/app/src/main/java/com/github/libretube/api/obj/SearchItem.kt +++ b/app/src/main/java/com/github/libretube/api/obj/ContentItem.kt @@ -3,7 +3,7 @@ package com.github.libretube.api.obj import com.fasterxml.jackson.annotation.JsonIgnoreProperties @JsonIgnoreProperties(ignoreUnknown = true) -data class SearchItem( +data class ContentItem( var url: String? = null, var thumbnail: String? = null, var uploaderName: String? = null, diff --git a/app/src/main/java/com/github/libretube/api/obj/SearchResult.kt b/app/src/main/java/com/github/libretube/api/obj/SearchResult.kt index 42ff66005..8ec7699f0 100644 --- a/app/src/main/java/com/github/libretube/api/obj/SearchResult.kt +++ b/app/src/main/java/com/github/libretube/api/obj/SearchResult.kt @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties @JsonIgnoreProperties(ignoreUnknown = true) data class SearchResult( - val items: MutableList? = arrayListOf(), + val items: MutableList? = arrayListOf(), val nextpage: String? = "", val suggestion: String? = "", val corrected: Boolean? = null diff --git a/app/src/main/java/com/github/libretube/ui/adapters/SearchAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/SearchAdapter.kt index fcc71b98e..9043446b1 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/SearchAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/SearchAdapter.kt @@ -8,7 +8,7 @@ import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView import com.github.libretube.R import com.github.libretube.api.SubscriptionHelper -import com.github.libretube.api.obj.SearchItem +import com.github.libretube.api.obj.ContentItem import com.github.libretube.databinding.ChannelRowBinding import com.github.libretube.databinding.PlaylistSearchRowBinding import com.github.libretube.databinding.VideoRowBinding @@ -26,12 +26,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class SearchAdapter( - private val searchItems: MutableList, + private val searchItems: MutableList, private val childFragmentManager: FragmentManager ) : RecyclerView.Adapter() { - fun updateItems(newItems: List) { + fun updateItems(newItems: List) { val searchItemsSize = searchItems.size searchItems.addAll(newItems) notifyItemRangeInserted(searchItemsSize, newItems.size) @@ -81,7 +81,7 @@ class SearchAdapter( } } - private fun bindWatch(item: SearchItem, binding: VideoRowBinding) { + private fun bindWatch(item: ContentItem, binding: VideoRowBinding) { binding.apply { ImageHelper.loadImage(item.thumbnail, thumbnail) thumbnailDuration.setFormattedDuration(item.duration!!) @@ -114,7 +114,7 @@ class SearchAdapter( @SuppressLint("SetTextI18n") private fun bindChannel( - item: SearchItem, + item: ContentItem, binding: ChannelRowBinding ) { binding.apply { @@ -164,7 +164,7 @@ class SearchAdapter( } private fun bindPlaylist( - item: SearchItem, + item: ContentItem, binding: PlaylistSearchRowBinding ) { binding.apply { 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 4f9584f2e..d5d276857 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 @@ -171,6 +171,14 @@ class ChannelFragment : BaseFragment() { ) binding.channelRecView.adapter = channelAdapter } + + response.tabs?.forEach { + try { + RetrofitInstance.api.getChannelTab(it.data!!).toString() + } catch (e: Exception) { + Log.e("error", e.toString()) + } + } } } From 18eebe663bd0131ec5128a38d5cb5c07b71b2192 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 29 Oct 2022 12:06:25 +0200 Subject: [PATCH 2/3] UI for channel tabs --- .../libretube/ui/fragments/ChannelFragment.kt | 89 ++++++++++++++++--- app/src/main/res/layout/fragment_channel.xml | 41 +++++++++ app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/style.xml | 9 ++ 4 files changed, 130 insertions(+), 10 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 d5d276857..5e94096eb 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 @@ -6,11 +6,13 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.core.view.children import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.github.libretube.R import com.github.libretube.api.RetrofitInstance import com.github.libretube.api.SubscriptionHelper +import com.github.libretube.api.obj.ChannelTab import com.github.libretube.constants.IntentData import com.github.libretube.constants.ShareObjectType import com.github.libretube.databinding.FragmentChannelBinding @@ -18,9 +20,14 @@ import com.github.libretube.extensions.TAG import com.github.libretube.extensions.formatShort import com.github.libretube.extensions.toID import com.github.libretube.ui.adapters.ChannelAdapter +import com.github.libretube.ui.adapters.SearchAdapter import com.github.libretube.ui.base.BaseFragment import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.util.ImageHelper +import com.google.android.material.chip.Chip +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import retrofit2.HttpException import java.io.IOException @@ -35,6 +42,10 @@ class ChannelFragment : BaseFragment() { private var isLoading = true private var isSubscribed: Boolean? = false + private var onScrollEnd: () -> Unit = {} + + val scope = CoroutineScope(Dispatchers.IO) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { @@ -73,11 +84,10 @@ class ChannelFragment : BaseFragment() { if (binding.channelScrollView.getChildAt(0).bottom == (binding.channelScrollView.height + binding.channelScrollView.scrollY) ) { - // scroll view is at bottom - if (nextPage != null && !isLoading) { - isLoading = true - binding.channelRefresh.isRefreshing = true - fetchChannelNextPage() + try { + onScrollEnd.invoke() + } catch (e: Exception) { + Log.e("tabs failed", e.toString()) } } } @@ -103,6 +113,14 @@ class ChannelFragment : BaseFragment() { // needed if the channel gets loaded by the ID channelId = response.id + onScrollEnd = { + if (nextPage != null && !isLoading) { + isLoading = true + binding.channelRefresh.isRefreshing = true + fetchChannelNextPage() + } + } + // fetch and update the subscription status isSubscribed = SubscriptionHelper.isSubscribed(channelId!!) if (isSubscribed == null) return@launchWhenCreated @@ -166,17 +184,68 @@ class ChannelFragment : BaseFragment() { // recyclerview of the videos by the channel channelAdapter = ChannelAdapter( - response.relatedStreams!!.toMutableList(), + response.relatedStreams.orEmpty().toMutableList(), childFragmentManager ) binding.channelRecView.adapter = channelAdapter } - response.tabs?.forEach { - try { - RetrofitInstance.api.getChannelTab(it.data!!).toString() + binding.videos.setOnClickListener { + binding.channelRecView.adapter = channelAdapter + } + + response.tabs?.firstOrNull { it.name == "Playlists" }?.let { + setupTab(binding.playlists, it) + } + + response.tabs?.firstOrNull { it.name == "Channels" }?.let { + setupTab(binding.channels, it) + } + + response.tabs?.firstOrNull { it.name == "Livestreams" }?.let { + setupTab(binding.livestreams, it) + } + + response.tabs?.firstOrNull { it.name == "Shorts" }?.let { + setupTab(binding.shorts, it) + } + } + } + + private fun setupTab(chip: Chip, tab: ChannelTab) { + chip.visibility = View.VISIBLE + chip.setOnClickListener { + binding.tabChips.children.forEach { + if (it != chip) it.isSelected = false + } + scope.launch { + val response = try { + RetrofitInstance.api.getChannelTab(tab.data!!) } catch (e: Exception) { - Log.e("error", e.toString()) + return@launch + } + + val adapter = SearchAdapter( + response.content.toMutableList(), + childFragmentManager + ) + + runOnUiThread { + binding.channelRecView.adapter = adapter + } + + onScrollEnd = { + if (response.nextpage != null) { + scope.launch { + val newContent = try { + RetrofitInstance.api.getChannelTab(tab.data, response.nextpage) + } catch (e: Exception) { + e.printStackTrace() + null + } + newContent?.content?.let { adapter.updateItems(it) } + } + } } } } diff --git a/app/src/main/res/layout/fragment_channel.xml b/app/src/main/res/layout/fragment_channel.xml index 70bdf6509..6d4889444 100644 --- a/app/src/main/res/layout/fragment_channel.xml +++ b/app/src/main/res/layout/fragment_channel.xml @@ -115,6 +115,47 @@ android:maxLines="2" android:padding="10dp" /> + + + + + + + + + + + + + + + + + + Queue Markers Mark the segments on the time bar. + Livestreams Download Service diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml index a1858f15e..e5837b1e8 100644 --- a/app/src/main/res/values/style.xml +++ b/app/src/main/res/values/style.xml @@ -179,4 +179,13 @@ slide + + \ No newline at end of file From 8119462cb753fb91580675bc585f6428874dd644 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 29 Oct 2022 12:16:48 +0200 Subject: [PATCH 3/3] Fixed minor bugs and improved Chip UI --- .../com/github/libretube/ui/fragments/ChannelFragment.kt | 8 ++++++-- app/src/main/res/layout/fragment_channel.xml | 3 ++- 2 files changed, 8 insertions(+), 3 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 5e94096eb..9ddb36bc3 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 @@ -216,7 +216,7 @@ class ChannelFragment : BaseFragment() { chip.visibility = View.VISIBLE chip.setOnClickListener { binding.tabChips.children.forEach { - if (it != chip) it.isSelected = false + if (it != chip) (it as Chip).isChecked = false } scope.launch { val response = try { @@ -243,7 +243,11 @@ class ChannelFragment : BaseFragment() { e.printStackTrace() null } - newContent?.content?.let { adapter.updateItems(it) } + runOnUiThread { + newContent?.content?.let { + adapter.updateItems(it) + } + } } } } diff --git a/app/src/main/res/layout/fragment_channel.xml b/app/src/main/res/layout/fragment_channel.xml index 6d4889444..ab26d98aa 100644 --- a/app/src/main/res/layout/fragment_channel.xml +++ b/app/src/main/res/layout/fragment_channel.xml @@ -116,19 +116,20 @@ android:padding="10dp" />