refactor: split video and trending layout into different adapters

This commit is contained in:
Bnyro 2025-04-15 15:38:03 +02:00
parent ca56ab6a11
commit 77515bf55a
No known key found for this signature in database
10 changed files with 158 additions and 138 deletions

View File

@ -0,0 +1,119 @@
package com.github.libretube.ui.adapters
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.ListAdapter
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.AllCaughtUpRowBinding
import com.github.libretube.databinding.TrendingRowBinding
import com.github.libretube.extensions.dpToPx
import com.github.libretube.extensions.toID
import com.github.libretube.helpers.ImageHelper
import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.ui.adapters.callbacks.DiffUtilItemCallback
import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.extensions.setFormattedDuration
import com.github.libretube.ui.extensions.setWatchProgressLength
import com.github.libretube.ui.sheets.VideoOptionsBottomSheet
import com.github.libretube.ui.viewholders.VideoCardsViewHolder
import com.github.libretube.util.TextUtils
class VideoCardsAdapter(private val columnWidthDp: Float? = null) :
ListAdapter<StreamItem, VideoCardsViewHolder>(DiffUtilItemCallback()) {
override fun getItemViewType(position: Int): Int {
return if (currentList[position].type == CAUGHT_UP_STREAM_TYPE) CAUGHT_UP_TYPE else NORMAL_TYPE
}
fun removeItemById(videoId: String) {
val index = currentList.indexOfFirst {
it.url?.toID() == videoId
}.takeIf { it > 0 } ?: return
val updatedList = currentList.toMutableList().also {
it.removeAt(index)
}
submitList(updatedList)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoCardsViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return when {
viewType == CAUGHT_UP_TYPE -> VideoCardsViewHolder(
AllCaughtUpRowBinding.inflate(layoutInflater, parent, false)
)
else -> VideoCardsViewHolder(
TrendingRowBinding.inflate(layoutInflater, parent, false)
)
}
}
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: VideoCardsViewHolder, position: Int) {
val video = getItem(holder.bindingAdapterPosition)
val videoId = video.url.orEmpty().toID()
val context = (holder.trendingRowBinding ?: holder.allCaughtUpBinding)!!.root.context
val activity = (context as BaseActivity)
val fragmentManager = activity.supportFragmentManager
holder.trendingRowBinding?.apply {
// set a fixed width for better visuals
if (columnWidthDp != null) {
root.updateLayoutParams {
width = columnWidthDp.dpToPx()
}
}
watchProgress.setWatchProgressLength(videoId, video.duration ?: 0L)
textViewTitle.text = video.title
textViewChannel.text = TextUtils.formatViewsString(
root.context,
video.views ?: -1,
video.uploaded,
video.uploaderName
)
video.duration?.let {
thumbnailDuration.setFormattedDuration(
it,
video.isShort,
video.uploaded
)
}
channelImage.setOnClickListener {
NavigationHelper.navigateChannel(root.context, video.uploaderUrl)
}
ImageHelper.loadImage(video.thumbnail, thumbnail)
ImageHelper.loadImage(video.uploaderAvatar, channelImage, true)
root.setOnClickListener {
NavigationHelper.navigateVideo(root.context, videoId)
}
root.setOnLongClickListener {
fragmentManager.setFragmentResultListener(
VideoOptionsBottomSheet.VIDEO_OPTIONS_SHEET_REQUEST_KEY,
activity
) { _, _ ->
notifyItemChanged(position)
}
val sheet = VideoOptionsBottomSheet()
sheet.arguments = bundleOf(IntentData.streamItem to video)
sheet.show(fragmentManager, VideoCardsAdapter::class.java.name)
true
}
}
}
companion object {
private const val NORMAL_TYPE = 0
private const val CAUGHT_UP_TYPE = 1
const val CAUGHT_UP_STREAM_TYPE = "caught"
}
}

View File

@ -6,15 +6,11 @@ import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.ListAdapter
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.AllCaughtUpRowBinding
import com.github.libretube.databinding.TrendingRowBinding
import com.github.libretube.databinding.VideoRowBinding
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.extensions.dpToPx
import com.github.libretube.extensions.toID
import com.github.libretube.helpers.ImageHelper
import com.github.libretube.helpers.NavigationHelper
@ -31,13 +27,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class VideosAdapter(
private val forceMode: LayoutMode = LayoutMode.RESPECT_PREF
private val showChannelInfo: Boolean = true
) : ListAdapter<StreamItem, VideosViewHolder>(DiffUtilItemCallback()) {
override fun getItemViewType(position: Int): Int {
return if (currentList[position].type == CAUGHT_UP_STREAM_TYPE) CAUGHT_UP_TYPE else NORMAL_TYPE
}
fun insertItems(newItems: List<StreamItem>) {
val updatedList = currentList.toMutableList().also {
it.addAll(newItems)
@ -46,37 +38,10 @@ class VideosAdapter(
submitList(updatedList)
}
fun removeItemById(videoId: String) {
val index = currentList.indexOfFirst {
it.url?.toID() == videoId
}.takeIf { it > 0 } ?: return
val updatedList = currentList.toMutableList().also {
it.removeAt(index)
}
submitList(updatedList)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideosViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return when {
viewType == CAUGHT_UP_TYPE -> VideosViewHolder(
AllCaughtUpRowBinding.inflate(layoutInflater, parent, false)
)
forceMode in listOf(
LayoutMode.TRENDING_ROW,
LayoutMode.RELATED_COLUMN
) -> VideosViewHolder(
TrendingRowBinding.inflate(layoutInflater, parent, false)
)
forceMode == LayoutMode.CHANNEL_ROW -> VideosViewHolder(
VideoRowBinding.inflate(layoutInflater, parent, false)
)
else -> VideosViewHolder(TrendingRowBinding.inflate(layoutInflater, parent, false))
}
val binding = VideoRowBinding.inflate(layoutInflater, parent, false)
return VideosViewHolder(binding)
}
@SuppressLint("SetTextI18n")
@ -84,51 +49,11 @@ class VideosAdapter(
val video = getItem(holder.bindingAdapterPosition)
val videoId = video.url.orEmpty().toID()
val context = (
holder.videoRowBinding ?: holder.trendingRowBinding ?: holder.allCaughtUpBinding
)!!.root.context
val context = holder.binding.root.context
val activity = (context as BaseActivity)
val fragmentManager = activity.supportFragmentManager
// Trending layout
holder.trendingRowBinding?.apply {
// set a fixed width for better visuals
if (forceMode == LayoutMode.RELATED_COLUMN) {
root.updateLayoutParams {
width = 250f.dpToPx()
}
}
watchProgress.setWatchProgressLength(videoId, video.duration ?: 0L)
textViewTitle.text = video.title
textViewChannel.text = TextUtils.formatViewsString(root.context, video.views ?: -1, video.uploaded, video.uploaderName)
video.duration?.let { thumbnailDuration.setFormattedDuration(it, video.isShort, video.uploaded) }
channelImage.setOnClickListener {
NavigationHelper.navigateChannel(root.context, video.uploaderUrl)
}
ImageHelper.loadImage(video.thumbnail, thumbnail)
ImageHelper.loadImage(video.uploaderAvatar, channelImage, true)
root.setOnClickListener {
NavigationHelper.navigateVideo(root.context, videoId)
}
root.setOnLongClickListener {
fragmentManager.setFragmentResultListener(
VideoOptionsBottomSheet.VIDEO_OPTIONS_SHEET_REQUEST_KEY,
activity
) { _, _ ->
notifyItemChanged(position)
}
val sheet = VideoOptionsBottomSheet()
sheet.arguments = bundleOf(IntentData.streamItem to video)
sheet.show(fragmentManager, VideosAdapter::class.java.name)
true
}
}
// Normal videos row layout
holder.videoRowBinding?.apply {
with(holder.binding) {
videoTitle.text = video.title
videoInfo.text = TextUtils.formatViewsString(root.context, video.views ?: -1, video.uploaded)
@ -136,7 +61,7 @@ class VideosAdapter(
watchProgress.setWatchProgressLength(videoId, video.duration ?: 0L)
ImageHelper.loadImage(video.thumbnail, thumbnail)
if (forceMode != LayoutMode.CHANNEL_ROW) {
if (showChannelInfo) {
ImageHelper.loadImage(video.uploaderAvatar, channelImage, true)
channelName.text = video.uploaderName
@ -174,19 +99,4 @@ class VideosAdapter(
}
}
}
companion object {
enum class LayoutMode {
RESPECT_PREF,
TRENDING_ROW,
VIDEO_ROW,
CHANNEL_ROW,
RELATED_COLUMN
}
private const val NORMAL_TYPE = 0
private const val CAUGHT_UP_TYPE = 1
const val CAUGHT_UP_STREAM_TYPE = "caught"
}
}

View File

@ -60,9 +60,7 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch
var nextPage = arguments.getString(IntentData.nextPage)
var isLoading = false
val channelAdapter = VideosAdapter(
forceMode = VideosAdapter.Companion.LayoutMode.CHANNEL_ROW
).also {
val channelAdapter = VideosAdapter(showChannelInfo = false).also {
it.submitList(arguments.parcelableArrayList<StreamItem>(IntentData.videoList)!!)
}
binding.channelRecView.adapter = channelAdapter

View File

@ -234,9 +234,7 @@ class ChannelFragment : Fragment(R.layout.fragment_channel) {
tab.text = tabList[position].name
}.attach()
channelAdapter = VideosAdapter(
forceMode = VideosAdapter.Companion.LayoutMode.CHANNEL_ROW
).also {
channelAdapter = VideosAdapter(showChannelInfo = false).also {
it.submitList(response.relatedStreams)
}
tabList.clear()

View File

@ -23,8 +23,7 @@ import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.ui.activities.SettingsActivity
import com.github.libretube.ui.adapters.PlaylistBookmarkAdapter
import com.github.libretube.ui.adapters.PlaylistsAdapter
import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.adapters.VideosAdapter.Companion.LayoutMode
import com.github.libretube.ui.adapters.VideoCardsAdapter
import com.github.libretube.ui.extensions.setupFragmentAnimation
import com.github.libretube.ui.models.HomeViewModel
import com.github.libretube.ui.models.SubscriptionsViewModel
@ -38,9 +37,9 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
private val subscriptionsViewModel: SubscriptionsViewModel by activityViewModels()
private val homeViewModel: HomeViewModel by activityViewModels()
private val trendingAdapter = VideosAdapter(forceMode = LayoutMode.TRENDING_ROW)
private val feedAdapter = VideosAdapter(forceMode = LayoutMode.RELATED_COLUMN)
private val watchingAdapter = VideosAdapter(forceMode = LayoutMode.RELATED_COLUMN)
private val trendingAdapter = VideoCardsAdapter()
private val feedAdapter = VideoCardsAdapter(columnWidthDp = 250f)
private val watchingAdapter = VideoCardsAdapter(columnWidthDp = 250f)
private val bookmarkAdapter = PlaylistBookmarkAdapter(PlaylistBookmarkAdapter.Companion.BookmarkMode.HOME)
private val playlistAdapter = PlaylistsAdapter(playlistType = PlaylistsHelper.getPrivatePlaylistType())

View File

@ -89,7 +89,7 @@ import com.github.libretube.parcelable.PlayerData
import com.github.libretube.services.AbstractPlayerService
import com.github.libretube.services.OnlinePlayerService
import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.adapters.VideoCardsAdapter
import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.dialogs.AddToPlaylistDialog
import com.github.libretube.ui.dialogs.PlayOfflineDialog
@ -1127,12 +1127,8 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions {
if (PlayerHelper.relatedStreamsEnabled) {
val relatedLayoutManager = binding.relatedRecView.layoutManager as LinearLayoutManager
binding.relatedRecView.adapter = VideosAdapter(
forceMode = if (relatedLayoutManager.orientation == LinearLayoutManager.HORIZONTAL) {
VideosAdapter.Companion.LayoutMode.RELATED_COLUMN
} else {
VideosAdapter.Companion.LayoutMode.TRENDING_ROW
}
binding.relatedRecView.adapter = VideoCardsAdapter(
columnWidthDp = if (relatedLayoutManager.orientation == LinearLayoutManager.HORIZONTAL) 250f else null
).also { adapter ->
adapter.submitList(streams.relatedStreams.filter { !it.title.isNullOrBlank() })
}

View File

@ -32,7 +32,7 @@ import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.obj.SelectableOption
import com.github.libretube.ui.adapters.LegacySubscriptionAdapter
import com.github.libretube.ui.adapters.SubscriptionChannelAdapter
import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.adapters.VideoCardsAdapter
import com.github.libretube.ui.base.DynamicLayoutManagerFragment
import com.github.libretube.ui.extensions.addOnBottomReachedListener
import com.github.libretube.ui.extensions.setupFragmentAnimation
@ -62,7 +62,7 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
private var isAppBarFullyExpanded = true
private var feedAdapter = VideosAdapter()
private var feedAdapter = VideoCardsAdapter()
private var selectedSortOrder = PreferenceHelper.getInt(PreferenceKeys.FEED_SORT_ORDER, 0)
set(value) {
PreferenceHelper.putInt(PreferenceKeys.FEED_SORT_ORDER, value)
@ -393,7 +393,7 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
if (caughtUpIndex > 0 && !feed[caughtUpIndex-1].isUpcoming) {
sorted.add(
caughtUpIndex,
StreamItem(type = VideosAdapter.CAUGHT_UP_STREAM_TYPE)
StreamItem(type = VideoCardsAdapter.CAUGHT_UP_STREAM_TYPE)
)
}
}

View File

@ -12,7 +12,7 @@ import com.github.libretube.R
import com.github.libretube.databinding.FragmentTrendsBinding
import com.github.libretube.helpers.NavBarHelper
import com.github.libretube.ui.activities.SettingsActivity
import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.adapters.VideoCardsAdapter
import com.github.libretube.ui.base.DynamicLayoutManagerFragment
import com.github.libretube.ui.extensions.setupFragmentAnimation
import com.github.libretube.ui.models.TrendsViewModel
@ -31,7 +31,7 @@ class TrendsFragment : DynamicLayoutManagerFragment(R.layout.fragment_trends) {
_binding = FragmentTrendsBinding.bind(view)
super.onViewCreated(view, savedInstanceState)
val adapter = VideosAdapter()
val adapter = VideoCardsAdapter()
binding.recview.adapter = adapter
binding.recview.layoutManager?.onRestoreInstanceState(viewModel.recyclerViewState)

View File

@ -0,0 +1,18 @@
package com.github.libretube.ui.viewholders
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.databinding.AllCaughtUpRowBinding
import com.github.libretube.databinding.TrendingRowBinding
class VideoCardsViewHolder : RecyclerView.ViewHolder {
var trendingRowBinding: TrendingRowBinding? = null
var allCaughtUpBinding: AllCaughtUpRowBinding? = null
constructor(binding: TrendingRowBinding) : super(binding.root) {
trendingRowBinding = binding
}
constructor(binding: AllCaughtUpRowBinding) : super(binding.root) {
allCaughtUpBinding = binding
}
}

View File

@ -1,24 +1,6 @@
package com.github.libretube.ui.viewholders
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.databinding.AllCaughtUpRowBinding
import com.github.libretube.databinding.TrendingRowBinding
import com.github.libretube.databinding.VideoRowBinding
class VideosViewHolder : RecyclerView.ViewHolder {
var trendingRowBinding: TrendingRowBinding? = null
var videoRowBinding: VideoRowBinding? = null
var allCaughtUpBinding: AllCaughtUpRowBinding? = null
constructor(binding: TrendingRowBinding) : super(binding.root) {
trendingRowBinding = binding
}
constructor(binding: VideoRowBinding) : super(binding.root) {
videoRowBinding = binding
}
constructor(binding: AllCaughtUpRowBinding) : super(binding.root) {
allCaughtUpBinding = binding
}
}
class VideosViewHolder(val binding: VideoRowBinding) : RecyclerView.ViewHolder(binding.root)