mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 07:50:31 +05:30
refactor: rework RecyclerViews to set adapter once (#6971)
This commit is contained in:
parent
87aca083a6
commit
0cf7abb07d
@ -2,21 +2,29 @@ package com.github.libretube.ui.activities
|
|||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.databinding.VideoTagRowBinding
|
import com.github.libretube.databinding.VideoTagRowBinding
|
||||||
import com.github.libretube.ui.viewholders.VideoTagsViewHolder
|
import com.github.libretube.ui.viewholders.VideoTagsViewHolder
|
||||||
|
|
||||||
class VideoTagsAdapter(private val tags: List<String>) :
|
class VideoTagsAdapter :
|
||||||
RecyclerView.Adapter<VideoTagsViewHolder>() {
|
ListAdapter<String, VideoTagsViewHolder>(object : DiffUtil.ItemCallback<String>() {
|
||||||
|
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoTagsViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoTagsViewHolder {
|
||||||
val binding = VideoTagRowBinding.inflate(LayoutInflater.from(parent.context))
|
val binding = VideoTagRowBinding.inflate(LayoutInflater.from(parent.context))
|
||||||
return VideoTagsViewHolder(binding)
|
return VideoTagsViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = tags.size
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: VideoTagsViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: VideoTagsViewHolder, position: Int) {
|
||||||
val tag = tags[position]
|
val tag = getItem(holder.bindingAdapterPosition)
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
tagText.text = tag
|
tagText.text = tag
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
|
@ -47,13 +47,16 @@ class WelcomeActivity : BaseActivity() {
|
|||||||
val binding = ActivityWelcomeBinding.inflate(layoutInflater)
|
val binding = ActivityWelcomeBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
binding.instancesRecycler.layoutManager = LinearLayoutManager(this@WelcomeActivity)
|
||||||
|
val adapter = InstancesAdapter(viewModel.selectedInstanceIndex.value) { index ->
|
||||||
|
viewModel.selectedInstanceIndex.value = index
|
||||||
|
binding.okay.alpha = 1f
|
||||||
|
}
|
||||||
|
binding.instancesRecycler.adapter = adapter
|
||||||
|
|
||||||
// ALl the binding values are optional due to two different possible layouts (normal, landscape)
|
// ALl the binding values are optional due to two different possible layouts (normal, landscape)
|
||||||
viewModel.instances.observe(this) { instances ->
|
viewModel.instances.observe(this) { instances ->
|
||||||
binding.instancesRecycler.layoutManager = LinearLayoutManager(this@WelcomeActivity)
|
adapter.submitList(ImmutableList.copyOf(instances))
|
||||||
binding.instancesRecycler.adapter = InstancesAdapter(ImmutableList.copyOf(instances), viewModel.selectedInstanceIndex.value) { index ->
|
|
||||||
viewModel.selectedInstanceIndex.value = index
|
|
||||||
binding.okay.alpha = 1f
|
|
||||||
}
|
|
||||||
binding.progress.isGone = true
|
binding.progress.isGone = true
|
||||||
}
|
}
|
||||||
viewModel.fetchInstances()
|
viewModel.fetchInstances()
|
||||||
|
@ -2,25 +2,35 @@ package com.github.libretube.ui.adapters
|
|||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.databinding.AddChannelToGroupRowBinding
|
import com.github.libretube.databinding.AddChannelToGroupRowBinding
|
||||||
import com.github.libretube.db.obj.SubscriptionGroup
|
import com.github.libretube.db.obj.SubscriptionGroup
|
||||||
import com.github.libretube.ui.viewholders.AddChannelToGroupViewHolder
|
import com.github.libretube.ui.viewholders.AddChannelToGroupViewHolder
|
||||||
|
|
||||||
class AddChannelToGroupAdapter(
|
class AddChannelToGroupAdapter(
|
||||||
private val channelGroups: MutableList<SubscriptionGroup>,
|
|
||||||
private val channelId: String
|
private val channelId: String
|
||||||
) : RecyclerView.Adapter<AddChannelToGroupViewHolder>() {
|
) : ListAdapter<SubscriptionGroup, AddChannelToGroupViewHolder>(object: DiffUtil.ItemCallback<SubscriptionGroup>() {
|
||||||
|
override fun areItemsTheSame(oldItem: SubscriptionGroup, newItem: SubscriptionGroup): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: SubscriptionGroup,
|
||||||
|
newItem: SubscriptionGroup
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddChannelToGroupViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddChannelToGroupViewHolder {
|
||||||
val layoutInflater = LayoutInflater.from(parent.context)
|
val layoutInflater = LayoutInflater.from(parent.context)
|
||||||
val binding = AddChannelToGroupRowBinding.inflate(layoutInflater, parent, false)
|
val binding = AddChannelToGroupRowBinding.inflate(layoutInflater, parent, false)
|
||||||
return AddChannelToGroupViewHolder(binding)
|
return AddChannelToGroupViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = channelGroups.size
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: AddChannelToGroupViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: AddChannelToGroupViewHolder, position: Int) {
|
||||||
val channelGroup = channelGroups[position]
|
val channelGroup = getItem(holder.bindingAdapterPosition)
|
||||||
|
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
groupName.text = channelGroup.name
|
groupName.text = channelGroup.name
|
||||||
|
@ -9,7 +9,8 @@ import android.view.ViewGroup
|
|||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.databinding.VideoRowBinding
|
import com.github.libretube.databinding.VideoRowBinding
|
||||||
@ -37,9 +38,21 @@ import kotlin.io.path.fileSize
|
|||||||
class DownloadsAdapter(
|
class DownloadsAdapter(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val downloadTab: DownloadTab,
|
private val downloadTab: DownloadTab,
|
||||||
private val downloads: MutableList<DownloadWithItems>,
|
|
||||||
private val toggleDownload: (DownloadWithItems) -> Boolean
|
private val toggleDownload: (DownloadWithItems) -> Boolean
|
||||||
) : RecyclerView.Adapter<DownloadsViewHolder>() {
|
) : ListAdapter<DownloadWithItems, DownloadsViewHolder>(object :
|
||||||
|
DiffUtil.ItemCallback<DownloadWithItems>() {
|
||||||
|
override fun areItemsTheSame(oldItem: DownloadWithItems, newItem: DownloadWithItems): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: DownloadWithItems,
|
||||||
|
newItem: DownloadWithItems
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadsViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadsViewHolder {
|
||||||
val binding = VideoRowBinding.inflate(
|
val binding = VideoRowBinding.inflate(
|
||||||
LayoutInflater.from(parent.context),
|
LayoutInflater.from(parent.context),
|
||||||
@ -51,8 +64,8 @@ class DownloadsAdapter(
|
|||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBindViewHolder(holder: DownloadsViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: DownloadsViewHolder, position: Int) {
|
||||||
val download = downloads[position].download
|
val download = getItem(holder.bindingAdapterPosition).download
|
||||||
val items = downloads[position].downloadItems
|
val items = getItem(holder.bindingAdapterPosition).downloadItems
|
||||||
|
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
fileSize.isVisible = true
|
fileSize.isVisible = true
|
||||||
@ -96,7 +109,7 @@ class DownloadsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
progressBar.setOnClickListener {
|
progressBar.setOnClickListener {
|
||||||
val isDownloading = toggleDownload(downloads[position])
|
val isDownloading = toggleDownload(getItem(holder.bindingAdapterPosition))
|
||||||
|
|
||||||
resumePauseBtn.setImageResource(
|
resumePauseBtn.setImageResource(
|
||||||
if (isDownloading) {
|
if (isDownloading) {
|
||||||
@ -113,7 +126,11 @@ class DownloadsAdapter(
|
|||||||
intent.putExtra(IntentData.videoId, download.videoId)
|
intent.putExtra(IntentData.videoId, download.videoId)
|
||||||
root.context.startActivity(intent)
|
root.context.startActivity(intent)
|
||||||
} else {
|
} else {
|
||||||
BackgroundHelper.playOnBackgroundOffline(root.context, download.videoId, downloadTab)
|
BackgroundHelper.playOnBackgroundOffline(
|
||||||
|
root.context,
|
||||||
|
download.videoId,
|
||||||
|
downloadTab
|
||||||
|
)
|
||||||
NavigationHelper.openAudioPlayerFragment(root.context, offlinePlayer = true)
|
NavigationHelper.openAudioPlayerFragment(root.context, offlinePlayer = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,11 +169,9 @@ class DownloadsAdapter(
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun itemAt(index: Int) = downloads[index]
|
|
||||||
|
|
||||||
fun deleteDownload(position: Int) {
|
fun deleteDownload(position: Int) {
|
||||||
val download = downloads[position].download
|
val download = getItem(position).download
|
||||||
val items = downloads[position].downloadItems
|
val items = getItem(position).downloadItems
|
||||||
|
|
||||||
items.forEach {
|
items.forEach {
|
||||||
it.path.deleteIfExists()
|
it.path.deleteIfExists()
|
||||||
@ -168,9 +183,9 @@ class DownloadsAdapter(
|
|||||||
runBlocking(Dispatchers.IO) {
|
runBlocking(Dispatchers.IO) {
|
||||||
DatabaseHolder.Database.downloadDao().deleteDownload(download)
|
DatabaseHolder.Database.downloadDao().deleteDownload(download)
|
||||||
}
|
}
|
||||||
downloads.removeAt(position)
|
submitList(currentList.toMutableList().also {
|
||||||
notifyItemRemoved(position)
|
it.removeAt(position)
|
||||||
notifyItemRangeChanged(position, itemCount)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreItem(position: Int) {
|
fun restoreItem(position: Int) {
|
||||||
@ -178,6 +193,4 @@ class DownloadsAdapter(
|
|||||||
notifyItemRemoved(position)
|
notifyItemRemoved(position)
|
||||||
notifyItemInserted(position)
|
notifyItemInserted(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = downloads.size
|
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,26 @@ package com.github.libretube.ui.adapters
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.obj.PipedInstance
|
import com.github.libretube.api.obj.PipedInstance
|
||||||
import com.github.libretube.databinding.InstanceRowBinding
|
import com.github.libretube.databinding.InstanceRowBinding
|
||||||
import com.github.libretube.ui.viewholders.InstancesViewHolder
|
import com.github.libretube.ui.viewholders.InstancesViewHolder
|
||||||
import com.google.common.collect.ImmutableList
|
|
||||||
|
|
||||||
class InstancesAdapter(
|
class InstancesAdapter(
|
||||||
private val instances: ImmutableList<PipedInstance>,
|
|
||||||
initialSelectionApiIndex: Int?,
|
initialSelectionApiIndex: Int?,
|
||||||
private val onSelectInstance: (index: Int) -> Unit
|
private val onSelectInstance: (index: Int) -> Unit
|
||||||
) : RecyclerView.Adapter<InstancesViewHolder>() {
|
) : ListAdapter<PipedInstance, InstancesViewHolder>(object: DiffUtil.ItemCallback<PipedInstance>() {
|
||||||
|
override fun areItemsTheSame(oldItem: PipedInstance, newItem: PipedInstance): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: PipedInstance, newItem: PipedInstance): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
private var selectedInstanceIndex = initialSelectionApiIndex?.takeIf { it >= 0 }
|
private var selectedInstanceIndex = initialSelectionApiIndex?.takeIf { it >= 0 }
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InstancesViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InstancesViewHolder {
|
||||||
@ -23,11 +31,9 @@ class InstancesAdapter(
|
|||||||
return InstancesViewHolder(binding)
|
return InstancesViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = instances.size
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBindViewHolder(holder: InstancesViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: InstancesViewHolder, position: Int) {
|
||||||
val instance = instances[position]
|
val instance = getItem(holder.bindingAdapterPosition)
|
||||||
|
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
var instanceText = "${instance.name} ${instance.locations}"
|
var instanceText = "${instance.name} ${instance.locations}"
|
||||||
|
@ -3,7 +3,9 @@ package com.github.libretube.ui.adapters
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import com.github.libretube.api.obj.Subscription
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.databinding.LegacySubscriptionChannelBinding
|
import com.github.libretube.databinding.LegacySubscriptionChannelBinding
|
||||||
import com.github.libretube.extensions.toID
|
import com.github.libretube.extensions.toID
|
||||||
@ -13,9 +15,17 @@ import com.github.libretube.ui.base.BaseActivity
|
|||||||
import com.github.libretube.ui.sheets.ChannelOptionsBottomSheet
|
import com.github.libretube.ui.sheets.ChannelOptionsBottomSheet
|
||||||
import com.github.libretube.ui.viewholders.LegacySubscriptionViewHolder
|
import com.github.libretube.ui.viewholders.LegacySubscriptionViewHolder
|
||||||
|
|
||||||
class LegacySubscriptionAdapter(
|
class LegacySubscriptionAdapter : ListAdapter<Subscription, LegacySubscriptionViewHolder>(object :
|
||||||
private val subscriptions: List<com.github.libretube.api.obj.Subscription>
|
DiffUtil.ItemCallback<Subscription>() {
|
||||||
) : RecyclerView.Adapter<LegacySubscriptionViewHolder>() {
|
override fun areItemsTheSame(oldItem: Subscription, newItem: Subscription): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Subscription, newItem: Subscription): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
@ -27,7 +37,7 @@ class LegacySubscriptionAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: LegacySubscriptionViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: LegacySubscriptionViewHolder, position: Int) {
|
||||||
val subscription = subscriptions[position]
|
val subscription = getItem(holder.bindingAdapterPosition)
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
channelName.text = subscription.name
|
channelName.text = subscription.name
|
||||||
ImageHelper.loadImage(
|
ImageHelper.loadImage(
|
||||||
@ -51,6 +61,4 @@ class LegacySubscriptionAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = subscriptions.size
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,8 @@ import android.view.LayoutInflater
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.databinding.PlaylistBookmarkRowBinding
|
import com.github.libretube.databinding.PlaylistBookmarkRowBinding
|
||||||
@ -23,9 +24,17 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class PlaylistBookmarkAdapter(
|
class PlaylistBookmarkAdapter(
|
||||||
private val bookmarks: List<PlaylistBookmark>,
|
|
||||||
private val bookmarkMode: BookmarkMode = BookmarkMode.FRAGMENT
|
private val bookmarkMode: BookmarkMode = BookmarkMode.FRAGMENT
|
||||||
) : RecyclerView.Adapter<PlaylistBookmarkViewHolder>() {
|
) : ListAdapter<PlaylistBookmark, PlaylistBookmarkViewHolder>(object: DiffUtil.ItemCallback<PlaylistBookmark>() {
|
||||||
|
override fun areItemsTheSame(oldItem: PlaylistBookmark, newItem: PlaylistBookmark): Boolean {
|
||||||
|
return oldItem.playlistId == newItem.playlistId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: PlaylistBookmark, newItem: PlaylistBookmark): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistBookmarkViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistBookmarkViewHolder {
|
||||||
val layoutInflater = LayoutInflater.from(parent.context)
|
val layoutInflater = LayoutInflater.from(parent.context)
|
||||||
return when (bookmarkMode) {
|
return when (bookmarkMode) {
|
||||||
@ -39,8 +48,6 @@ class PlaylistBookmarkAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = bookmarks.size
|
|
||||||
|
|
||||||
private fun showPlaylistOptions(context: Context, bookmark: PlaylistBookmark) {
|
private fun showPlaylistOptions(context: Context, bookmark: PlaylistBookmark) {
|
||||||
val sheet = PlaylistOptionsBottomSheet()
|
val sheet = PlaylistOptionsBottomSheet()
|
||||||
sheet.arguments = bundleOf(
|
sheet.arguments = bundleOf(
|
||||||
@ -54,7 +61,7 @@ class PlaylistBookmarkAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: PlaylistBookmarkViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: PlaylistBookmarkViewHolder, position: Int) {
|
||||||
val bookmark = bookmarks[position]
|
val bookmark = getItem(holder.bindingAdapterPosition)
|
||||||
holder.playlistBookmarkBinding?.apply {
|
holder.playlistBookmarkBinding?.apply {
|
||||||
ImageHelper.loadImage(bookmark.thumbnailUrl, thumbnail)
|
ImageHelper.loadImage(bookmark.thumbnailUrl, thumbnail)
|
||||||
playlistName.text = bookmark.playlistName
|
playlistName.text = bookmark.playlistName
|
||||||
|
@ -3,7 +3,8 @@ package com.github.libretube.ui.adapters
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.obj.Playlists
|
import com.github.libretube.api.obj.Playlists
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
@ -17,18 +18,18 @@ import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet.Companion.PLAYL
|
|||||||
import com.github.libretube.ui.viewholders.PlaylistsViewHolder
|
import com.github.libretube.ui.viewholders.PlaylistsViewHolder
|
||||||
|
|
||||||
class PlaylistsAdapter(
|
class PlaylistsAdapter(
|
||||||
private val playlists: MutableList<Playlists>,
|
|
||||||
private val playlistType: PlaylistType
|
private val playlistType: PlaylistType
|
||||||
) : RecyclerView.Adapter<PlaylistsViewHolder>() {
|
) : ListAdapter<Playlists, PlaylistsViewHolder>(object : DiffUtil.ItemCallback<Playlists>() {
|
||||||
|
override fun areItemsTheSame(oldItem: Playlists, newItem: Playlists): Boolean {
|
||||||
override fun getItemCount() = playlists.size
|
return oldItem.id == newItem.id
|
||||||
|
|
||||||
fun updateItems(newItems: List<Playlists>) {
|
|
||||||
val oldSize = playlists.size
|
|
||||||
playlists.addAll(newItems)
|
|
||||||
notifyItemRangeInserted(oldSize, playlists.size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Playlists, newItem: Playlists): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistsViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistsViewHolder {
|
||||||
val layoutInflater = LayoutInflater.from(parent.context)
|
val layoutInflater = LayoutInflater.from(parent.context)
|
||||||
val binding = PlaylistsRowBinding.inflate(layoutInflater, parent, false)
|
val binding = PlaylistsRowBinding.inflate(layoutInflater, parent, false)
|
||||||
@ -36,7 +37,7 @@ class PlaylistsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: PlaylistsViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: PlaylistsViewHolder, position: Int) {
|
||||||
val playlist = playlists[position]
|
val playlist = getItem(holder.bindingAdapterPosition)
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
// set imageview drawable as empty playlist if imageview empty
|
// set imageview drawable as empty playlist if imageview empty
|
||||||
if (playlist.thumbnail.orEmpty().split("/").size <= 4) {
|
if (playlist.thumbnail.orEmpty().split("/").size <= 4) {
|
||||||
@ -80,7 +81,7 @@ class PlaylistsAdapter(
|
|||||||
|
|
||||||
if (isPlaylistToBeDeleted) {
|
if (isPlaylistToBeDeleted) {
|
||||||
// try to refresh the playlists in the library on deletion success
|
// try to refresh the playlists in the library on deletion success
|
||||||
onDelete(position, root.context as BaseActivity)
|
onDelete(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,11 +100,10 @@ class PlaylistsAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onDelete(position: Int, activity: BaseActivity) {
|
private fun onDelete(position: Int) {
|
||||||
playlists.removeAt(position)
|
val newList = currentList.toMutableList().also {
|
||||||
activity.runOnUiThread {
|
it.removeAt(position)
|
||||||
notifyItemRemoved(position)
|
|
||||||
notifyItemRangeChanged(position, itemCount)
|
|
||||||
}
|
}
|
||||||
|
submitList(newList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@ package com.github.libretube.ui.adapters
|
|||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.SearchView
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.databinding.SuggestionRowBinding
|
import com.github.libretube.databinding.SuggestionRowBinding
|
||||||
import com.github.libretube.db.DatabaseHolder.Database
|
import com.github.libretube.db.DatabaseHolder.Database
|
||||||
import com.github.libretube.db.obj.SearchHistoryItem
|
import com.github.libretube.db.obj.SearchHistoryItem
|
||||||
@ -13,13 +13,18 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
class SearchHistoryAdapter(
|
class SearchHistoryAdapter(
|
||||||
private var historyList: List<String>,
|
private val onRootClickListener: (String) -> Unit,
|
||||||
private val searchView: SearchView
|
private val onArrowClickListener: (String) -> Unit,
|
||||||
) :
|
) : ListAdapter<String, SuggestionsViewHolder>(object: DiffUtil.ItemCallback<String>() {
|
||||||
RecyclerView.Adapter<SuggestionsViewHolder>() {
|
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemCount() = historyList.size
|
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SuggestionsViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SuggestionsViewHolder {
|
||||||
val layoutInflater = LayoutInflater.from(parent.context)
|
val layoutInflater = LayoutInflater.from(parent.context)
|
||||||
val binding = SuggestionRowBinding.inflate(layoutInflater, parent, false)
|
val binding = SuggestionRowBinding.inflate(layoutInflater, parent, false)
|
||||||
@ -27,26 +32,28 @@ class SearchHistoryAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: SuggestionsViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SuggestionsViewHolder, position: Int) {
|
||||||
val historyQuery = historyList[position]
|
val historyQuery = getItem(holder.bindingAdapterPosition)
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
suggestionText.text = historyQuery
|
suggestionText.text = historyQuery
|
||||||
|
|
||||||
deleteHistory.isVisible = true
|
deleteHistory.isVisible = true
|
||||||
|
|
||||||
deleteHistory.setOnClickListener {
|
deleteHistory.setOnClickListener {
|
||||||
historyList -= historyQuery
|
val updatedList = currentList.toMutableList().also {
|
||||||
|
it.remove(historyQuery)
|
||||||
|
}
|
||||||
runBlocking(Dispatchers.IO) {
|
runBlocking(Dispatchers.IO) {
|
||||||
Database.searchHistoryDao().delete(SearchHistoryItem(historyQuery))
|
Database.searchHistoryDao().delete(SearchHistoryItem(historyQuery))
|
||||||
}
|
}
|
||||||
notifyItemRemoved(position)
|
|
||||||
notifyItemRangeChanged(position, itemCount)
|
submitList(updatedList)
|
||||||
}
|
}
|
||||||
|
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
searchView.setQuery(historyQuery, true)
|
onRootClickListener(historyQuery)
|
||||||
}
|
}
|
||||||
arrow.setOnClickListener {
|
arrow.setOnClickListener {
|
||||||
searchView.setQuery(historyQuery, false)
|
onArrowClickListener(historyQuery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,24 @@ package com.github.libretube.ui.adapters
|
|||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.databinding.SuggestionRowBinding
|
import com.github.libretube.databinding.SuggestionRowBinding
|
||||||
import com.github.libretube.ui.viewholders.SuggestionsViewHolder
|
import com.github.libretube.ui.viewholders.SuggestionsViewHolder
|
||||||
|
|
||||||
class SearchSuggestionsAdapter(
|
class SearchSuggestionsAdapter(
|
||||||
private var suggestionsList: List<String>,
|
private val onRootClickListener: (String) -> Unit,
|
||||||
private val searchView: SearchView
|
private val onArrowClickListener: (String) -> Unit,
|
||||||
) :
|
) : ListAdapter<String, SuggestionsViewHolder>(object: DiffUtil.ItemCallback<String>() {
|
||||||
RecyclerView.Adapter<SuggestionsViewHolder>() {
|
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemCount() = suggestionsList.size
|
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SuggestionsViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SuggestionsViewHolder {
|
||||||
val layoutInflater = LayoutInflater.from(parent.context)
|
val layoutInflater = LayoutInflater.from(parent.context)
|
||||||
@ -22,14 +28,14 @@ class SearchSuggestionsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: SuggestionsViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SuggestionsViewHolder, position: Int) {
|
||||||
val suggestion = suggestionsList[position]
|
val suggestion = getItem(holder.bindingAdapterPosition)
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
suggestionText.text = suggestion
|
suggestionText.text = suggestion
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
searchView.setQuery(suggestion, true)
|
onRootClickListener(suggestion)
|
||||||
}
|
}
|
||||||
arrow.setOnClickListener {
|
arrow.setOnClickListener {
|
||||||
searchView.setQuery(suggestion, false)
|
onArrowClickListener(suggestion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ package com.github.libretube.ui.adapters
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.api.obj.Subscription
|
import com.github.libretube.api.obj.Subscription
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.databinding.ChannelSubscriptionRowBinding
|
import com.github.libretube.databinding.ChannelSubscriptionRowBinding
|
||||||
@ -15,12 +16,20 @@ import com.github.libretube.ui.extensions.setupSubscriptionButton
|
|||||||
import com.github.libretube.ui.sheets.ChannelOptionsBottomSheet
|
import com.github.libretube.ui.sheets.ChannelOptionsBottomSheet
|
||||||
import com.github.libretube.ui.viewholders.SubscriptionChannelViewHolder
|
import com.github.libretube.ui.viewholders.SubscriptionChannelViewHolder
|
||||||
|
|
||||||
class SubscriptionChannelAdapter(
|
class SubscriptionChannelAdapter : ListAdapter<Subscription, SubscriptionChannelViewHolder>(object :
|
||||||
private val subscriptions: MutableList<Subscription>
|
DiffUtil.ItemCallback<Subscription>() {
|
||||||
) : RecyclerView.Adapter<SubscriptionChannelViewHolder>() {
|
override fun areItemsTheSame(oldItem: Subscription, newItem: Subscription): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Subscription, newItem: Subscription): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
private var visibleCount = 20
|
private var visibleCount = 20
|
||||||
|
|
||||||
override fun getItemCount() = minOf(visibleCount, subscriptions.size)
|
override fun getItemCount() = minOf(visibleCount, currentList.size)
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
@ -33,13 +42,13 @@ class SubscriptionChannelAdapter(
|
|||||||
|
|
||||||
fun updateItems() {
|
fun updateItems() {
|
||||||
val oldSize = visibleCount
|
val oldSize = visibleCount
|
||||||
visibleCount += minOf(10, subscriptions.size - oldSize)
|
visibleCount += minOf(10, currentList.size - oldSize)
|
||||||
if (visibleCount == oldSize) return
|
if (visibleCount == oldSize) return
|
||||||
notifyItemRangeInserted(oldSize, visibleCount)
|
notifyItemRangeInserted(oldSize, visibleCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: SubscriptionChannelViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SubscriptionChannelViewHolder, position: Int) {
|
||||||
val subscription = subscriptions[position]
|
val subscription = getItem(holder.bindingAdapterPosition)
|
||||||
|
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
subscriptionChannelName.text = subscription.name
|
subscriptionChannelName.text = subscription.name
|
||||||
|
@ -2,7 +2,8 @@ package com.github.libretube.ui.adapters
|
|||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.api.obj.Subscription
|
import com.github.libretube.api.obj.Subscription
|
||||||
import com.github.libretube.databinding.SubscriptionGroupChannelRowBinding
|
import com.github.libretube.databinding.SubscriptionGroupChannelRowBinding
|
||||||
import com.github.libretube.db.obj.SubscriptionGroup
|
import com.github.libretube.db.obj.SubscriptionGroup
|
||||||
@ -12,10 +13,18 @@ import com.github.libretube.helpers.NavigationHelper
|
|||||||
import com.github.libretube.ui.viewholders.SubscriptionGroupChannelRowViewHolder
|
import com.github.libretube.ui.viewholders.SubscriptionGroupChannelRowViewHolder
|
||||||
|
|
||||||
class SubscriptionGroupChannelsAdapter(
|
class SubscriptionGroupChannelsAdapter(
|
||||||
private val channels: List<Subscription>,
|
|
||||||
private val group: SubscriptionGroup,
|
private val group: SubscriptionGroup,
|
||||||
private val onGroupChanged: (SubscriptionGroup) -> Unit
|
private val onGroupChanged: (SubscriptionGroup) -> Unit
|
||||||
) : RecyclerView.Adapter<SubscriptionGroupChannelRowViewHolder>() {
|
) : ListAdapter<Subscription, SubscriptionGroupChannelRowViewHolder>(object: DiffUtil.ItemCallback<Subscription>() {
|
||||||
|
override fun areItemsTheSame(oldItem: Subscription, newItem: Subscription): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: Subscription, newItem: Subscription): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int
|
viewType: Int
|
||||||
@ -25,10 +34,8 @@ class SubscriptionGroupChannelsAdapter(
|
|||||||
return SubscriptionGroupChannelRowViewHolder(binding)
|
return SubscriptionGroupChannelRowViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = channels.size
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: SubscriptionGroupChannelRowViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SubscriptionGroupChannelRowViewHolder, position: Int) {
|
||||||
val channel = channels[position]
|
val channel = getItem(holder.bindingAdapterPosition)
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
NavigationHelper.navigateChannel(root.context, channel.url)
|
NavigationHelper.navigateChannel(root.context, channel.url)
|
||||||
|
@ -8,8 +8,9 @@ import androidx.core.os.bundleOf
|
|||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView.LayoutManager
|
import androidx.recyclerview.widget.RecyclerView.LayoutManager
|
||||||
import com.github.libretube.api.obj.StreamItem
|
import com.github.libretube.api.obj.StreamItem
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
@ -36,29 +37,38 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class VideosAdapter(
|
class VideosAdapter(
|
||||||
private val streamItems: MutableList<StreamItem>,
|
|
||||||
private val forceMode: LayoutMode = LayoutMode.RESPECT_PREF
|
private val forceMode: LayoutMode = LayoutMode.RESPECT_PREF
|
||||||
) : RecyclerView.Adapter<VideosViewHolder>() {
|
) : ListAdapter<StreamItem, VideosViewHolder>(object: DiffUtil.ItemCallback<StreamItem>() {
|
||||||
override fun getItemCount() = streamItems.size
|
override fun areItemsTheSame(oldItem: StreamItem, newItem: StreamItem): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: StreamItem, newItem: StreamItem): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return if (streamItems[position].type == CAUGHT_UP_STREAM_TYPE) CAUGHT_UP_TYPE else NORMAL_TYPE
|
return if (currentList[position].type == CAUGHT_UP_STREAM_TYPE) CAUGHT_UP_TYPE else NORMAL_TYPE
|
||||||
}
|
}
|
||||||
|
|
||||||
fun insertItems(newItems: List<StreamItem>) {
|
fun insertItems(newItems: List<StreamItem>) {
|
||||||
val feedSize = streamItems.size
|
val updatedList = currentList.toMutableList().also {
|
||||||
streamItems.addAll(newItems)
|
it.addAll(newItems)
|
||||||
notifyItemRangeInserted(feedSize, newItems.size)
|
}
|
||||||
|
|
||||||
|
submitList(updatedList)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeItemById(videoId: String) {
|
fun removeItemById(videoId: String) {
|
||||||
val index = streamItems.indexOfFirst {
|
val index = currentList.indexOfFirst {
|
||||||
it.url?.toID() == videoId
|
it.url?.toID() == videoId
|
||||||
}.takeIf { it > 0 } ?: return
|
}.takeIf { it > 0 } ?: return
|
||||||
streamItems.removeAt(index)
|
val updatedList = currentList.toMutableList().also {
|
||||||
|
it.removeAt(index)
|
||||||
|
}
|
||||||
|
|
||||||
notifyItemRemoved(index)
|
submitList(updatedList)
|
||||||
notifyItemRangeChanged(index, itemCount)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideosViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideosViewHolder {
|
||||||
@ -90,7 +100,7 @@ class VideosAdapter(
|
|||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onBindViewHolder(holder: VideosViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: VideosViewHolder, position: Int) {
|
||||||
val video = streamItems[position]
|
val video = getItem(holder.bindingAdapterPosition)
|
||||||
val videoId = video.url.orEmpty().toID()
|
val videoId = video.url.orEmpty().toID()
|
||||||
|
|
||||||
val context = (
|
val context = (
|
||||||
|
@ -5,7 +5,8 @@ import android.view.ViewGroup
|
|||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.databinding.VideoRowBinding
|
import com.github.libretube.databinding.VideoRowBinding
|
||||||
import com.github.libretube.db.DatabaseHolder
|
import com.github.libretube.db.DatabaseHolder
|
||||||
@ -24,27 +25,34 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class WatchHistoryAdapter(
|
class WatchHistoryAdapter : ListAdapter<WatchHistoryItem, WatchHistoryViewHolder>(object :
|
||||||
private val watchHistory: MutableList<WatchHistoryItem>
|
DiffUtil.ItemCallback<WatchHistoryItem>() {
|
||||||
) :
|
override fun areItemsTheSame(oldItem: WatchHistoryItem, newItem: WatchHistoryItem): Boolean {
|
||||||
RecyclerView.Adapter<WatchHistoryViewHolder>() {
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
override fun getItemCount() = watchHistory.size
|
override fun areContentsTheSame(oldItem: WatchHistoryItem, newItem: WatchHistoryItem): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}) {
|
||||||
|
|
||||||
fun removeFromWatchHistory(position: Int) {
|
fun removeFromWatchHistory(position: Int) {
|
||||||
val history = watchHistory[position]
|
val history = getItem(position)
|
||||||
runBlocking(Dispatchers.IO) {
|
runBlocking(Dispatchers.IO) {
|
||||||
DatabaseHolder.Database.watchHistoryDao().delete(history)
|
DatabaseHolder.Database.watchHistoryDao().delete(history)
|
||||||
}
|
}
|
||||||
watchHistory.removeAt(position)
|
val updatedList = currentList.toMutableList().also {
|
||||||
notifyItemRemoved(position)
|
it.removeAt(position)
|
||||||
notifyItemRangeChanged(position, itemCount)
|
}
|
||||||
|
submitList(updatedList)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun insertItems(items: List<WatchHistoryItem>) {
|
fun insertItems(items: List<WatchHistoryItem>) {
|
||||||
val oldSize = itemCount
|
val updatedList = currentList.toMutableList().also {
|
||||||
this.watchHistory.addAll(items)
|
it.addAll(items)
|
||||||
notifyItemRangeInserted(oldSize, itemCount)
|
}
|
||||||
|
submitList(updatedList)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WatchHistoryViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WatchHistoryViewHolder {
|
||||||
@ -54,7 +62,7 @@ class WatchHistoryAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: WatchHistoryViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: WatchHistoryViewHolder, position: Int) {
|
||||||
val video = watchHistory[position]
|
val video = getItem(holder.bindingAdapterPosition)
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
videoTitle.text = video.title
|
videoTitle.text = video.title
|
||||||
channelName.text = video.uploader
|
channelName.text = video.uploader
|
||||||
|
@ -87,10 +87,8 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch
|
|||||||
}
|
}
|
||||||
nextPage = response.nextpage
|
nextPage = response.nextpage
|
||||||
|
|
||||||
val binding = _binding ?: return@launch
|
|
||||||
searchChannelAdapter = SearchChannelAdapter()
|
|
||||||
binding.channelRecView.adapter = searchChannelAdapter
|
|
||||||
searchChannelAdapter?.submitList(response.content)
|
searchChannelAdapter?.submitList(response.content)
|
||||||
|
val binding = _binding ?: return@launch
|
||||||
binding.progressBar.isGone = true
|
binding.progressBar.isGone = true
|
||||||
|
|
||||||
isLoading = false
|
isLoading = false
|
||||||
@ -123,6 +121,9 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch
|
|||||||
channelId = arguments.getString(IntentData.channelId)
|
channelId = arguments.getString(IntentData.channelId)
|
||||||
nextPage = arguments.getString(IntentData.nextPage)
|
nextPage = arguments.getString(IntentData.nextPage)
|
||||||
|
|
||||||
|
searchChannelAdapter = SearchChannelAdapter()
|
||||||
|
binding.channelRecView.adapter = searchChannelAdapter
|
||||||
|
|
||||||
binding.channelRecView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
binding.channelRecView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||||
super.onScrollStateChanged(recyclerView, newState)
|
super.onScrollStateChanged(recyclerView, newState)
|
||||||
@ -147,9 +148,10 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch
|
|||||||
|
|
||||||
if (tabData?.data.isNullOrEmpty()) {
|
if (tabData?.data.isNullOrEmpty()) {
|
||||||
channelAdapter = VideosAdapter(
|
channelAdapter = VideosAdapter(
|
||||||
arguments.parcelableArrayList<StreamItem>(IntentData.videoList)!!,
|
|
||||||
forceMode = VideosAdapter.Companion.LayoutMode.CHANNEL_ROW
|
forceMode = VideosAdapter.Companion.LayoutMode.CHANNEL_ROW
|
||||||
)
|
).also {
|
||||||
|
it.submitList(arguments.parcelableArrayList<StreamItem>(IntentData.videoList)!!)
|
||||||
|
}
|
||||||
binding.channelRecView.adapter = channelAdapter
|
binding.channelRecView.adapter = channelAdapter
|
||||||
binding.progressBar.isGone = true
|
binding.progressBar.isGone = true
|
||||||
|
|
||||||
|
@ -236,9 +236,10 @@ class ChannelFragment : DynamicLayoutManagerFragment(R.layout.fragment_channel)
|
|||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
channelAdapter = VideosAdapter(
|
channelAdapter = VideosAdapter(
|
||||||
response.relatedStreams.toMutableList(),
|
|
||||||
forceMode = VideosAdapter.Companion.LayoutMode.CHANNEL_ROW
|
forceMode = VideosAdapter.Companion.LayoutMode.CHANNEL_ROW
|
||||||
)
|
).also {
|
||||||
|
it.submitList(response.relatedStreams)
|
||||||
|
}
|
||||||
tabList.clear()
|
tabList.clear()
|
||||||
|
|
||||||
val tabs = listOf(ChannelTab(VIDEOS_TAB_KEY, "")) + response.tabs
|
val tabs = listOf(ChannelTab(VIDEOS_TAB_KEY, "")) + response.tabs
|
||||||
|
@ -146,6 +146,32 @@ class DownloadsFragmentPage : DynamicLayoutManagerFragment(R.layout.fragment_dow
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
_binding = FragmentDownloadContentBinding.bind(view)
|
_binding = FragmentDownloadContentBinding.bind(view)
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
adapter = DownloadsAdapter(requireContext(), downloadTab) {
|
||||||
|
var isDownloading = false
|
||||||
|
val ids = it.downloadItems
|
||||||
|
.filter { item -> item.path.fileSize() < item.downloadSize }
|
||||||
|
.map { item -> item.id }
|
||||||
|
|
||||||
|
if (!serviceConnection.isBound) {
|
||||||
|
DownloadHelper.startDownloadService(requireContext())
|
||||||
|
bindDownloadService(ids.toIntArray())
|
||||||
|
return@DownloadsAdapter true
|
||||||
|
}
|
||||||
|
|
||||||
|
binder?.getService()?.let { service ->
|
||||||
|
isDownloading = ids.any { id -> service.isDownloading(id) }
|
||||||
|
|
||||||
|
ids.forEach { id ->
|
||||||
|
if (isDownloading) {
|
||||||
|
service.pause(id)
|
||||||
|
} else {
|
||||||
|
service.resume(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@DownloadsAdapter isDownloading.not()
|
||||||
|
}
|
||||||
|
binding.downloadsRecView.adapter = adapter
|
||||||
|
|
||||||
var selectedSortType =
|
var selectedSortType =
|
||||||
PreferenceHelper.getInt(PreferenceKeys.SELECTED_DOWNLOAD_SORT_TYPE, 0)
|
PreferenceHelper.getInt(PreferenceKeys.SELECTED_DOWNLOAD_SORT_TYPE, 0)
|
||||||
@ -156,7 +182,6 @@ class DownloadsFragmentPage : DynamicLayoutManagerFragment(R.layout.fragment_dow
|
|||||||
binding.sortType.text = filterOptions[index]
|
binding.sortType.text = filterOptions[index]
|
||||||
if (::adapter.isInitialized) {
|
if (::adapter.isInitialized) {
|
||||||
sortDownloadList(index, selectedSortType)
|
sortDownloadList(index, selectedSortType)
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
selectedSortType = index
|
selectedSortType = index
|
||||||
PreferenceHelper.putInt(
|
PreferenceHelper.putInt(
|
||||||
@ -178,32 +203,6 @@ class DownloadsFragmentPage : DynamicLayoutManagerFragment(R.layout.fragment_dow
|
|||||||
|
|
||||||
sortDownloadList(selectedSortType)
|
sortDownloadList(selectedSortType)
|
||||||
|
|
||||||
adapter = DownloadsAdapter(requireContext(), downloadTab, downloads) {
|
|
||||||
var isDownloading = false
|
|
||||||
val ids = it.downloadItems
|
|
||||||
.filter { item -> item.path.fileSize() < item.downloadSize }
|
|
||||||
.map { item -> item.id }
|
|
||||||
|
|
||||||
if (!serviceConnection.isBound) {
|
|
||||||
DownloadHelper.startDownloadService(requireContext())
|
|
||||||
bindDownloadService(ids.toIntArray())
|
|
||||||
return@DownloadsAdapter true
|
|
||||||
}
|
|
||||||
|
|
||||||
binder?.getService()?.let { service ->
|
|
||||||
isDownloading = ids.any { id -> service.isDownloading(id) }
|
|
||||||
|
|
||||||
ids.forEach { id ->
|
|
||||||
if (isDownloading) {
|
|
||||||
service.pause(id)
|
|
||||||
} else {
|
|
||||||
service.resume(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return@DownloadsAdapter isDownloading.not()
|
|
||||||
}
|
|
||||||
binding.downloadsRecView.adapter = adapter
|
|
||||||
|
|
||||||
binding.downloadsRecView.setOnDismissListener { position ->
|
binding.downloadsRecView.setOnDismissListener { position ->
|
||||||
adapter.showDeleteDialog(requireContext(), position)
|
adapter.showDeleteDialog(requireContext(), position)
|
||||||
@ -251,10 +250,10 @@ class DownloadsFragmentPage : DynamicLayoutManagerFragment(R.layout.fragment_dow
|
|||||||
|
|
||||||
private fun sortDownloadList(sortType: Int, previousSortType: Int? = null) {
|
private fun sortDownloadList(sortType: Int, previousSortType: Int? = null) {
|
||||||
if (previousSortType == null && sortType == 1) {
|
if (previousSortType == null && sortType == 1) {
|
||||||
downloads.reverse()
|
adapter.submitList(downloads.reversed())
|
||||||
}
|
}
|
||||||
if (previousSortType != null && sortType != previousSortType) {
|
if (previousSortType != null && sortType != previousSortType) {
|
||||||
downloads.reverse()
|
adapter.submitList(downloads.reversed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +268,7 @@ class DownloadsFragmentPage : DynamicLayoutManagerFragment(R.layout.fragment_dow
|
|||||||
.setPositiveButton(R.string.okay) { _, _ ->
|
.setPositiveButton(R.string.okay) { _, _ ->
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
for (downloadIndex in downloads.size - 1 downTo 0) {
|
for (downloadIndex in downloads.size - 1 downTo 0) {
|
||||||
val download = adapter.itemAt(downloadIndex).download
|
val download = adapter.currentList[downloadIndex].download
|
||||||
if (!onlyDeleteWatchedVideos || DatabaseHelper.isVideoWatched(download.videoId, download.duration ?: 0)) {
|
if (!onlyDeleteWatchedVideos || DatabaseHelper.isVideoWatched(download.videoId, download.duration ?: 0)) {
|
||||||
adapter.deleteDownload(downloadIndex)
|
adapter.deleteDownload(downloadIndex)
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,6 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.PlaylistsHelper
|
import com.github.libretube.api.PlaylistsHelper
|
||||||
@ -41,10 +38,32 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
|
|||||||
private val subscriptionsViewModel: SubscriptionsViewModel by activityViewModels()
|
private val subscriptionsViewModel: SubscriptionsViewModel by activityViewModels()
|
||||||
private val homeViewModel: HomeViewModel 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 bookmarkAdapter = PlaylistBookmarkAdapter(PlaylistBookmarkAdapter.Companion.BookmarkMode.HOME)
|
||||||
|
private val playlistAdapter = PlaylistsAdapter(playlistType = PlaylistsHelper.getPrivatePlaylistType())
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
_binding = FragmentHomeBinding.bind(view)
|
_binding = FragmentHomeBinding.bind(view)
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.trendingRV.adapter = trendingAdapter
|
||||||
|
binding.featuredRV.adapter = feedAdapter
|
||||||
|
binding.bookmarksRV.adapter = bookmarkAdapter
|
||||||
|
binding.playlistsRV.adapter = playlistAdapter
|
||||||
|
binding.playlistsRV.adapter?.registerAdapterDataObserver(object :
|
||||||
|
RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||||
|
super.onItemRangeRemoved(positionStart, itemCount)
|
||||||
|
if (itemCount == 0) {
|
||||||
|
binding.playlistsRV.isGone = true
|
||||||
|
binding.playlistsTV.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
binding.watchingRV.adapter = watchingAdapter
|
||||||
|
|
||||||
with(homeViewModel) {
|
with(homeViewModel) {
|
||||||
trending.observe(viewLifecycleOwner, ::showTrending)
|
trending.observe(viewLifecycleOwner, ::showTrending)
|
||||||
feed.observe(viewLifecycleOwner, ::showFeed)
|
feed.observe(viewLifecycleOwner, ::showFeed)
|
||||||
@ -123,11 +142,7 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
|
|||||||
if (streamItems == null) return
|
if (streamItems == null) return
|
||||||
|
|
||||||
makeVisible(binding.trendingRV, binding.trendingTV)
|
makeVisible(binding.trendingRV, binding.trendingTV)
|
||||||
binding.trendingRV.layoutManager = GridLayoutManager(context, 2)
|
trendingAdapter.submitList(streamItems)
|
||||||
binding.trendingRV.adapter = VideosAdapter(
|
|
||||||
streamItems.toMutableList(),
|
|
||||||
forceMode = LayoutMode.TRENDING_ROW
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showFeed(streamItems: List<StreamItem>?) {
|
private fun showFeed(streamItems: List<StreamItem>?) {
|
||||||
@ -138,57 +153,29 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
|
|||||||
val feedVideos = streamItems
|
val feedVideos = streamItems
|
||||||
.let { DatabaseHelper.filterByStatusAndWatchPosition(it, hideWatched) }
|
.let { DatabaseHelper.filterByStatusAndWatchPosition(it, hideWatched) }
|
||||||
.take(20)
|
.take(20)
|
||||||
.toMutableList()
|
|
||||||
|
|
||||||
with(binding.featuredRV) {
|
feedAdapter.submitList(feedVideos)
|
||||||
layoutManager = LinearLayoutManager(context, HORIZONTAL, false)
|
|
||||||
adapter = VideosAdapter(feedVideos, forceMode = LayoutMode.RELATED_COLUMN)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showBookmarks(bookmarks: List<PlaylistBookmark>?) {
|
private fun showBookmarks(bookmarks: List<PlaylistBookmark>?) {
|
||||||
if (bookmarks == null) return
|
if (bookmarks == null) return
|
||||||
|
|
||||||
makeVisible(binding.bookmarksTV, binding.bookmarksRV)
|
makeVisible(binding.bookmarksTV, binding.bookmarksRV)
|
||||||
with(binding.bookmarksRV) {
|
bookmarkAdapter.submitList(bookmarks)
|
||||||
layoutManager = LinearLayoutManager(context, HORIZONTAL, false)
|
|
||||||
adapter = PlaylistBookmarkAdapter(
|
|
||||||
bookmarks.toMutableList(),
|
|
||||||
PlaylistBookmarkAdapter.Companion.BookmarkMode.HOME
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showPlaylists(playlists: List<Playlists>?) {
|
private fun showPlaylists(playlists: List<Playlists>?) {
|
||||||
if (playlists == null) return
|
if (playlists == null) return
|
||||||
|
|
||||||
makeVisible(binding.playlistsRV, binding.playlistsTV)
|
makeVisible(binding.playlistsRV, binding.playlistsTV)
|
||||||
binding.playlistsRV.layoutManager = LinearLayoutManager(context)
|
playlistAdapter.submitList(playlists)
|
||||||
binding.playlistsRV.adapter = PlaylistsAdapter(
|
|
||||||
playlists.toMutableList(),
|
|
||||||
playlistType = PlaylistsHelper.getPrivatePlaylistType()
|
|
||||||
)
|
|
||||||
binding.playlistsRV.adapter?.registerAdapterDataObserver(object :
|
|
||||||
RecyclerView.AdapterDataObserver() {
|
|
||||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
|
||||||
super.onItemRangeRemoved(positionStart, itemCount)
|
|
||||||
if (itemCount == 0) {
|
|
||||||
binding.playlistsRV.isGone = true
|
|
||||||
binding.playlistsTV.isGone = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showContinueWatching(unwatchedVideos: List<StreamItem>?) {
|
private fun showContinueWatching(unwatchedVideos: List<StreamItem>?) {
|
||||||
if (unwatchedVideos == null) return
|
if (unwatchedVideos == null) return
|
||||||
|
|
||||||
makeVisible(binding.watchingRV, binding.watchingTV)
|
makeVisible(binding.watchingRV, binding.watchingTV)
|
||||||
binding.watchingRV.layoutManager = LinearLayoutManager(context, HORIZONTAL, false)
|
watchingAdapter.submitList(unwatchedVideos)
|
||||||
binding.watchingRV.adapter = VideosAdapter(
|
|
||||||
unwatchedVideos.toMutableList(),
|
|
||||||
forceMode = LayoutMode.RELATED_COLUMN
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLoading(isLoading: Boolean) {
|
private fun updateLoading(isLoading: Boolean) {
|
||||||
|
@ -45,6 +45,9 @@ class LibraryFragment : DynamicLayoutManagerFragment(R.layout.fragment_library)
|
|||||||
|
|
||||||
private val commonPlayerViewModel: CommonPlayerViewModel by activityViewModels()
|
private val commonPlayerViewModel: CommonPlayerViewModel by activityViewModels()
|
||||||
|
|
||||||
|
private val playlistsAdapter = PlaylistsAdapter(PlaylistsHelper.getPrivatePlaylistType())
|
||||||
|
private val playlistBookmarkAdapter = PlaylistBookmarkAdapter()
|
||||||
|
|
||||||
override fun setLayoutManagers(gridItems: Int) {
|
override fun setLayoutManagers(gridItems: Int) {
|
||||||
_binding?.bookmarksRecView?.layoutManager = GridLayoutManager(context, gridItems.ceilHalf())
|
_binding?.bookmarksRecView?.layoutManager = GridLayoutManager(context, gridItems.ceilHalf())
|
||||||
_binding?.playlistRecView?.layoutManager = GridLayoutManager(context, gridItems.ceilHalf())
|
_binding?.playlistRecView?.layoutManager = GridLayoutManager(context, gridItems.ceilHalf())
|
||||||
@ -54,6 +57,18 @@ class LibraryFragment : DynamicLayoutManagerFragment(R.layout.fragment_library)
|
|||||||
_binding = FragmentLibraryBinding.bind(view)
|
_binding = FragmentLibraryBinding.bind(view)
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.bookmarksRecView.adapter = playlistBookmarkAdapter
|
||||||
|
// listen for playlists to become deleted
|
||||||
|
playlistsAdapter.registerAdapterDataObserver(object :
|
||||||
|
RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||||
|
_binding?.nothingHere?.isVisible = playlistsAdapter.itemCount == 0
|
||||||
|
_binding?.sortTV?.isVisible = playlistsAdapter.itemCount > 0
|
||||||
|
super.onItemRangeRemoved(positionStart, itemCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
binding.playlistRecView.adapter = playlistsAdapter
|
||||||
|
|
||||||
// listen for the mini player state changing
|
// listen for the mini player state changing
|
||||||
commonPlayerViewModel.isMiniPlayerVisible.observe(viewLifecycleOwner) {
|
commonPlayerViewModel.isMiniPlayerVisible.observe(viewLifecycleOwner) {
|
||||||
updateFABMargin(it)
|
updateFABMargin(it)
|
||||||
@ -142,7 +157,7 @@ class LibraryFragment : DynamicLayoutManagerFragment(R.layout.fragment_library)
|
|||||||
|
|
||||||
binding.bookmarksCV.isVisible = bookmarks.isNotEmpty()
|
binding.bookmarksCV.isVisible = bookmarks.isNotEmpty()
|
||||||
if (bookmarks.isNotEmpty()) {
|
if (bookmarks.isNotEmpty()) {
|
||||||
binding.bookmarksRecView.adapter = PlaylistBookmarkAdapter(bookmarks)
|
playlistBookmarkAdapter.submitList(bookmarks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,23 +199,8 @@ class LibraryFragment : DynamicLayoutManagerFragment(R.layout.fragment_library)
|
|||||||
private fun showPlaylists(playlists: List<Playlists>) {
|
private fun showPlaylists(playlists: List<Playlists>) {
|
||||||
val binding = _binding ?: return
|
val binding = _binding ?: return
|
||||||
|
|
||||||
val playlistsAdapter = PlaylistsAdapter(
|
|
||||||
playlists.toMutableList(),
|
|
||||||
PlaylistsHelper.getPrivatePlaylistType()
|
|
||||||
)
|
|
||||||
|
|
||||||
// listen for playlists to become deleted
|
|
||||||
playlistsAdapter.registerAdapterDataObserver(object :
|
|
||||||
RecyclerView.AdapterDataObserver() {
|
|
||||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
|
||||||
_binding?.nothingHere?.isVisible = playlistsAdapter.itemCount == 0
|
|
||||||
_binding?.sortTV?.isVisible = playlistsAdapter.itemCount > 0
|
|
||||||
super.onItemRangeRemoved(positionStart, itemCount)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
binding.nothingHere.isGone = true
|
binding.nothingHere.isGone = true
|
||||||
binding.sortTV.isVisible = true
|
binding.sortTV.isVisible = true
|
||||||
binding.playlistRecView.adapter = playlistsAdapter
|
playlistsAdapter.submitList(playlists)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1128,13 +1128,14 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions {
|
|||||||
if (PlayerHelper.relatedStreamsEnabled) {
|
if (PlayerHelper.relatedStreamsEnabled) {
|
||||||
val relatedLayoutManager = binding.relatedRecView.layoutManager as LinearLayoutManager
|
val relatedLayoutManager = binding.relatedRecView.layoutManager as LinearLayoutManager
|
||||||
binding.relatedRecView.adapter = VideosAdapter(
|
binding.relatedRecView.adapter = VideosAdapter(
|
||||||
streams.relatedStreams.filter { !it.title.isNullOrBlank() }.toMutableList(),
|
|
||||||
forceMode = if (relatedLayoutManager.orientation == LinearLayoutManager.HORIZONTAL) {
|
forceMode = if (relatedLayoutManager.orientation == LinearLayoutManager.HORIZONTAL) {
|
||||||
VideosAdapter.Companion.LayoutMode.RELATED_COLUMN
|
VideosAdapter.Companion.LayoutMode.RELATED_COLUMN
|
||||||
} else {
|
} else {
|
||||||
VideosAdapter.Companion.LayoutMode.TRENDING_ROW
|
VideosAdapter.Companion.LayoutMode.TRENDING_ROW
|
||||||
}
|
}
|
||||||
)
|
).also { adapter ->
|
||||||
|
adapter.submitList(streams.relatedStreams.filter { !it.title.isNullOrBlank() })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the subscribed state
|
// update the subscribed state
|
||||||
|
@ -325,6 +325,7 @@ class PlaylistFragment : DynamicLayoutManagerFragment(R.layout.fragment_playlist
|
|||||||
playlistId,
|
playlistId,
|
||||||
playlistType
|
playlistType
|
||||||
)
|
)
|
||||||
|
// TODO make sure the adapter is set once in onViewCreated
|
||||||
binding.playlistRecView.adapter = playlistAdapter
|
binding.playlistRecView.adapter = playlistAdapter
|
||||||
|
|
||||||
// listen for playlist items to become deleted
|
// listen for playlist items to become deleted
|
||||||
|
@ -7,9 +7,10 @@ import androidx.core.view.isGone
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.map
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
@ -34,6 +35,27 @@ class SearchSuggestionsFragment : Fragment(R.layout.fragment_search_suggestions)
|
|||||||
private val viewModel: SearchViewModel by activityViewModels()
|
private val viewModel: SearchViewModel by activityViewModels()
|
||||||
private val mainActivity get() = activity as MainActivity
|
private val mainActivity get() = activity as MainActivity
|
||||||
|
|
||||||
|
private val historyAdapter = SearchHistoryAdapter(
|
||||||
|
onRootClickListener = { historyQuery ->
|
||||||
|
runCatching {
|
||||||
|
(activity as MainActivity?)?.searchView
|
||||||
|
}.getOrNull()?.setQuery(historyQuery, true)
|
||||||
|
},
|
||||||
|
onArrowClickListener = { historyQuery ->
|
||||||
|
runCatching {
|
||||||
|
(activity as MainActivity?)?.searchView
|
||||||
|
}.getOrNull()?.setQuery(historyQuery, false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
private val suggestionsAdapter = SearchSuggestionsAdapter(
|
||||||
|
onRootClickListener = { suggestion ->
|
||||||
|
(activity as MainActivity?)?.searchView?.setQuery(suggestion, true)
|
||||||
|
},
|
||||||
|
onArrowClickListener = { suggestion ->
|
||||||
|
(activity as MainActivity?)?.searchView?.setQuery(suggestion, false)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
viewModel.searchQuery.value = arguments?.getString(IntentData.query)
|
viewModel.searchQuery.value = arguments?.getString(IntentData.query)
|
||||||
@ -43,9 +65,15 @@ class SearchSuggestionsFragment : Fragment(R.layout.fragment_search_suggestions)
|
|||||||
_binding = FragmentSearchSuggestionsBinding.bind(view)
|
_binding = FragmentSearchSuggestionsBinding.bind(view)
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
binding.suggestionsRecycler.layoutManager = LinearLayoutManager(requireContext()).apply {
|
viewModel.searchQuery
|
||||||
reverseLayout = true
|
.map { it.isNullOrEmpty() }
|
||||||
stackFromEnd = true
|
.distinctUntilChanged()
|
||||||
|
.observe(viewLifecycleOwner) { isQueryEmpty ->
|
||||||
|
if (isQueryEmpty) {
|
||||||
|
binding.suggestionsRecycler.adapter = historyAdapter
|
||||||
|
} else if (PreferenceHelper.getBoolean(PreferenceKeys.SEARCH_SUGGESTIONS, true)) {
|
||||||
|
binding.suggestionsRecycler.adapter = suggestionsAdapter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// waiting for the query to change
|
// waiting for the query to change
|
||||||
@ -81,30 +109,19 @@ class SearchSuggestionsFragment : Fragment(R.layout.fragment_search_suggestions)
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
// only load the suggestions if the input field didn't get cleared yet
|
// only load the suggestions if the input field didn't get cleared yet
|
||||||
val suggestionsAdapter = SearchSuggestionsAdapter(
|
if (!viewModel.searchQuery.value.isNullOrEmpty()) {
|
||||||
response.reversed(),
|
suggestionsAdapter.submitList(response.reversed())
|
||||||
(activity as MainActivity).searchView
|
|
||||||
)
|
|
||||||
if (isAdded && !viewModel.searchQuery.value.isNullOrEmpty()) {
|
|
||||||
binding.suggestionsRecycler.adapter = suggestionsAdapter
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showHistory() {
|
private fun showHistory() {
|
||||||
val searchView = runCatching {
|
|
||||||
(activity as MainActivity).searchView
|
|
||||||
}.getOrNull()
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val historyList = withContext(Dispatchers.IO) {
|
val historyList = withContext(Dispatchers.IO) {
|
||||||
Database.searchHistoryDao().getAll().map { it.query }
|
Database.searchHistoryDao().getAll().map { it.query }
|
||||||
}
|
}
|
||||||
if (historyList.isNotEmpty() && searchView != null) {
|
if (historyList.isNotEmpty()) {
|
||||||
binding.suggestionsRecycler.adapter = SearchHistoryAdapter(
|
historyAdapter.submitList(historyList)
|
||||||
historyList,
|
|
||||||
searchView
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
binding.suggestionsRecycler.isGone = true
|
binding.suggestionsRecycler.isGone = true
|
||||||
binding.historyEmpty.isVisible = true
|
binding.historyEmpty.isVisible = true
|
||||||
|
@ -64,10 +64,9 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
private var isCurrentTabSubChannels = false
|
private var isCurrentTabSubChannels = false
|
||||||
private var isAppBarFullyExpanded = true
|
private var isAppBarFullyExpanded = true
|
||||||
|
|
||||||
private var feedAdapter: VideosAdapter? = null
|
private var feedAdapter = VideosAdapter()
|
||||||
private val sortedFeed: MutableList<StreamItem> = mutableListOf()
|
private val sortedFeed: MutableList<StreamItem> = mutableListOf()
|
||||||
|
|
||||||
private var channelsAdapter: SubscriptionChannelAdapter? = null
|
|
||||||
private var selectedSortOrder = PreferenceHelper.getInt(PreferenceKeys.FEED_SORT_ORDER, 0)
|
private var selectedSortOrder = PreferenceHelper.getInt(PreferenceKeys.FEED_SORT_ORDER, 0)
|
||||||
set(value) {
|
set(value) {
|
||||||
PreferenceHelper.putInt(PreferenceKeys.FEED_SORT_ORDER, value)
|
PreferenceHelper.putInt(PreferenceKeys.FEED_SORT_ORDER, value)
|
||||||
@ -84,6 +83,9 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
private var subChannelsRecyclerViewState: Parcelable? = null
|
private var subChannelsRecyclerViewState: Parcelable? = null
|
||||||
private var subFeedRecyclerViewState: Parcelable? = null
|
private var subFeedRecyclerViewState: Parcelable? = null
|
||||||
|
|
||||||
|
private val legacySubscriptionsAdapter = LegacySubscriptionAdapter()
|
||||||
|
private val channelsAdapter = SubscriptionChannelAdapter()
|
||||||
|
|
||||||
override fun setLayoutManagers(gridItems: Int) {
|
override fun setLayoutManagers(gridItems: Int) {
|
||||||
_binding?.subFeed?.layoutManager = VideosAdapter.getLayout(requireContext(), gridItems)
|
_binding?.subFeed?.layoutManager = VideosAdapter.getLayout(requireContext(), gridItems)
|
||||||
}
|
}
|
||||||
@ -94,6 +96,27 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
|
|
||||||
setupSortAndFilter()
|
setupSortAndFilter()
|
||||||
|
|
||||||
|
binding.subFeed.adapter = feedAdapter
|
||||||
|
|
||||||
|
val legacySubscriptions = PreferenceHelper.getBoolean(
|
||||||
|
PreferenceKeys.LEGACY_SUBSCRIPTIONS,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
if (legacySubscriptions) {
|
||||||
|
binding.subChannels.layoutManager = GridLayoutManager(
|
||||||
|
context,
|
||||||
|
PreferenceHelper.getString(
|
||||||
|
PreferenceKeys.LEGACY_SUBSCRIPTIONS_COLUMNS,
|
||||||
|
"4"
|
||||||
|
).toInt()
|
||||||
|
)
|
||||||
|
binding.subChannels.adapter = legacySubscriptionsAdapter
|
||||||
|
} else {
|
||||||
|
binding.subChannels.layoutManager = LinearLayoutManager(context)
|
||||||
|
binding.subChannels.adapter = channelsAdapter
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the AppBarLayout is fully expanded
|
// Check if the AppBarLayout is fully expanded
|
||||||
binding.subscriptionsAppBar.addOnOffsetChangedListener { _, verticalOffset ->
|
binding.subscriptionsAppBar.addOnOffsetChangedListener { _, verticalOffset ->
|
||||||
isAppBarFullyExpanded = verticalOffset == 0
|
isAppBarFullyExpanded = verticalOffset == 0
|
||||||
@ -145,7 +168,7 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
|
|
||||||
if (viewModel.subscriptions.value != null && isCurrentTabSubChannels) {
|
if (viewModel.subscriptions.value != null && isCurrentTabSubChannels) {
|
||||||
binding.subRefresh.isRefreshing = true
|
binding.subRefresh.isRefreshing = true
|
||||||
channelsAdapter?.updateItems()
|
channelsAdapter.updateItems()
|
||||||
binding.subRefresh.isRefreshing = false
|
binding.subRefresh.isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,8 +227,6 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
private fun loadNextFeedItems() {
|
private fun loadNextFeedItems() {
|
||||||
val binding = _binding ?: return
|
val binding = _binding ?: return
|
||||||
|
|
||||||
val feedAdapter = feedAdapter ?: return
|
|
||||||
|
|
||||||
val hasMore = sortedFeed.size > feedAdapter.itemCount
|
val hasMore = sortedFeed.size > feedAdapter.itemCount
|
||||||
if (viewModel.videoFeed.value != null && !isCurrentTabSubChannels && !binding.subRefresh.isRefreshing && hasMore) {
|
if (viewModel.videoFeed.value != null && !isCurrentTabSubChannels && !binding.subRefresh.isRefreshing && hasMore) {
|
||||||
binding.subRefresh.isRefreshing = true
|
binding.subRefresh.isRefreshing = true
|
||||||
@ -373,11 +394,8 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
val notLoaded = viewModel.videoFeed.value.isNullOrEmpty()
|
val notLoaded = viewModel.videoFeed.value.isNullOrEmpty()
|
||||||
binding.subFeed.isGone = notLoaded
|
binding.subFeed.isGone = notLoaded
|
||||||
binding.emptyFeed.isVisible = notLoaded
|
binding.emptyFeed.isVisible = notLoaded
|
||||||
|
|
||||||
feedAdapter = VideosAdapter(mutableListOf())
|
|
||||||
loadNextFeedItems()
|
loadNextFeedItems()
|
||||||
|
|
||||||
binding.subFeed.adapter = feedAdapter
|
|
||||||
binding.toggleSubs.text = getString(R.string.subscriptions)
|
binding.toggleSubs.text = getString(R.string.subscriptions)
|
||||||
|
|
||||||
PreferenceHelper.updateLastFeedWatchedTime()
|
PreferenceHelper.updateLastFeedWatchedTime()
|
||||||
@ -393,18 +411,9 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (legacySubscriptions) {
|
if (legacySubscriptions) {
|
||||||
binding.subChannels.layoutManager = GridLayoutManager(
|
legacySubscriptionsAdapter.submitList(subscriptions)
|
||||||
context,
|
|
||||||
PreferenceHelper.getString(
|
|
||||||
PreferenceKeys.LEGACY_SUBSCRIPTIONS_COLUMNS,
|
|
||||||
"4"
|
|
||||||
).toInt()
|
|
||||||
)
|
|
||||||
binding.subChannels.adapter = LegacySubscriptionAdapter(subscriptions)
|
|
||||||
} else {
|
} else {
|
||||||
binding.subChannels.layoutManager = LinearLayoutManager(context)
|
channelsAdapter.submitList(subscriptions)
|
||||||
channelsAdapter = SubscriptionChannelAdapter(subscriptions.toMutableList())
|
|
||||||
binding.subChannels.adapter = channelsAdapter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.subRefresh.isRefreshing = false
|
binding.subRefresh.isRefreshing = false
|
||||||
@ -420,7 +429,7 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun removeItem(videoId: String) {
|
fun removeItem(videoId: String) {
|
||||||
feedAdapter?.removeItemById(videoId)
|
feedAdapter.removeItemById(videoId)
|
||||||
sortedFeed.removeAll { it.url?.toID() != videoId }
|
sortedFeed.removeAll { it.url?.toID() != videoId }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,15 +30,18 @@ class TrendsFragment : DynamicLayoutManagerFragment(R.layout.fragment_trends) {
|
|||||||
_binding = FragmentTrendsBinding.bind(view)
|
_binding = FragmentTrendsBinding.bind(view)
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
val adapter = VideosAdapter()
|
||||||
|
binding.recview.adapter = adapter
|
||||||
|
binding.recview.layoutManager?.onRestoreInstanceState(viewModel.recyclerViewState)
|
||||||
|
|
||||||
viewModel.trendingVideos.observe(viewLifecycleOwner) { videos ->
|
viewModel.trendingVideos.observe(viewLifecycleOwner) { videos ->
|
||||||
if (videos == null) return@observe
|
if (videos == null) return@observe
|
||||||
|
|
||||||
binding.recview.adapter = VideosAdapter(videos.toMutableList())
|
|
||||||
binding.recview.layoutManager?.onRestoreInstanceState(viewModel.recyclerViewState)
|
|
||||||
|
|
||||||
binding.homeRefresh.isRefreshing = false
|
binding.homeRefresh.isRefreshing = false
|
||||||
binding.progressBar.isGone = true
|
binding.progressBar.isGone = true
|
||||||
|
|
||||||
|
adapter.submitList(videos)
|
||||||
|
|
||||||
if (videos.isEmpty()) {
|
if (videos.isEmpty()) {
|
||||||
Snackbar.make(binding.root, R.string.change_region, Snackbar.LENGTH_LONG)
|
Snackbar.make(binding.root, R.string.change_region, Snackbar.LENGTH_LONG)
|
||||||
.setAction(R.string.settings) {
|
.setAction(R.string.settings) {
|
||||||
|
@ -50,6 +50,8 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc
|
|||||||
private var isLoading = false
|
private var isLoading = false
|
||||||
private var recyclerViewState: Parcelable? = null
|
private var recyclerViewState: Parcelable? = null
|
||||||
|
|
||||||
|
private val watchHistoryAdapter = WatchHistoryAdapter()
|
||||||
|
|
||||||
private var selectedStatusFilter = PreferenceHelper.getInt(
|
private var selectedStatusFilter = PreferenceHelper.getInt(
|
||||||
PreferenceKeys.SELECTED_HISTORY_STATUS_FILTER,
|
PreferenceKeys.SELECTED_HISTORY_STATUS_FILTER,
|
||||||
0
|
0
|
||||||
@ -80,6 +82,31 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc
|
|||||||
_binding?.watchHistoryRecView?.updatePadding(bottom = if (it) 64f.dpToPx() else 0)
|
_binding?.watchHistoryRecView?.updatePadding(bottom = if (it) 64f.dpToPx() else 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.watchHistoryRecView.setOnDismissListener { position ->
|
||||||
|
watchHistoryAdapter.removeFromWatchHistory(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// observe changes to indicate if the history is empty
|
||||||
|
watchHistoryAdapter.registerAdapterDataObserver(object :
|
||||||
|
RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||||
|
if (watchHistoryAdapter.itemCount == 0) {
|
||||||
|
binding.historyContainer.isGone = true
|
||||||
|
binding.historyEmpty.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.watchHistoryRecView.adapter = watchHistoryAdapter
|
||||||
|
|
||||||
|
// manually restore the recyclerview state due to https://github.com/material-components/material-components-android/issues/3473
|
||||||
|
binding.watchHistoryRecView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState)
|
||||||
|
recyclerViewState = binding.watchHistoryRecView.layoutManager?.onSaveInstanceState()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val history = withContext(Dispatchers.IO) {
|
val history = withContext(Dispatchers.IO) {
|
||||||
DatabaseHelper.getWatchHistoryPage(1, HISTORY_PAGE_SIZE)
|
DatabaseHelper.getWatchHistoryPage(1, HISTORY_PAGE_SIZE)
|
||||||
@ -139,14 +166,6 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc
|
|||||||
}.show(childFragmentManager)
|
}.show(childFragmentManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
// manually restore the recyclerview state due to https://github.com/material-components/material-components-android/issues/3473
|
|
||||||
binding.watchHistoryRecView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
|
||||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
|
||||||
super.onScrollStateChanged(recyclerView, newState)
|
|
||||||
recyclerViewState = binding.watchHistoryRecView.layoutManager?.onSaveInstanceState()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
showWatchHistory(history)
|
showWatchHistory(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +176,6 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc
|
|||||||
|
|
||||||
private fun showWatchHistory(history: List<WatchHistoryItem>) {
|
private fun showWatchHistory(history: List<WatchHistoryItem>) {
|
||||||
val watchHistory = history.filterByStatusAndWatchPosition()
|
val watchHistory = history.filterByStatusAndWatchPosition()
|
||||||
val watchHistoryAdapter = WatchHistoryAdapter(watchHistory.toMutableList())
|
|
||||||
|
|
||||||
binding.playAll.setOnClickListener {
|
binding.playAll.setOnClickListener {
|
||||||
PlayingQueue.resetToDefaults()
|
PlayingQueue.resetToDefaults()
|
||||||
@ -170,26 +188,10 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc
|
|||||||
keepQueue = true
|
keepQueue = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
watchHistoryAdapter.submitList(history)
|
||||||
binding.watchHistoryRecView.adapter = watchHistoryAdapter
|
|
||||||
binding.historyEmpty.isGone = true
|
binding.historyEmpty.isGone = true
|
||||||
binding.historyContainer.isVisible = true
|
binding.historyContainer.isVisible = true
|
||||||
|
|
||||||
binding.watchHistoryRecView.setOnDismissListener { position ->
|
|
||||||
watchHistoryAdapter.removeFromWatchHistory(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
// observe changes to indicate if the history is empty
|
|
||||||
watchHistoryAdapter.registerAdapterDataObserver(object :
|
|
||||||
RecyclerView.AdapterDataObserver() {
|
|
||||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
|
||||||
if (watchHistoryAdapter.itemCount == 0) {
|
|
||||||
binding.historyContainer.isGone = true
|
|
||||||
binding.historyEmpty.isVisible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// add a listener for scroll end, delay needed to prevent loading new ones the first time
|
// add a listener for scroll end, delay needed to prevent loading new ones the first time
|
||||||
handler.postDelayed(200) {
|
handler.postDelayed(200) {
|
||||||
if (_binding == null) return@postDelayed
|
if (_binding == null) return@postDelayed
|
||||||
|
@ -187,8 +187,10 @@ class InstanceSettings : BasePreferenceFragment() {
|
|||||||
binding.optionsRecycler.layoutManager = LinearLayoutManager(context)
|
binding.optionsRecycler.layoutManager = LinearLayoutManager(context)
|
||||||
|
|
||||||
val instances = ImmutableList.copyOf(this.instances)
|
val instances = ImmutableList.copyOf(this.instances)
|
||||||
binding.optionsRecycler.adapter = InstancesAdapter(instances, selectedIndex) {
|
binding.optionsRecycler.adapter = InstancesAdapter(selectedIndex) {
|
||||||
selectedInstance = instances[it].apiUrl
|
selectedInstance = instances[it].apiUrl
|
||||||
|
}.also {
|
||||||
|
it.submitList(instances)
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
@ -3,7 +3,6 @@ package com.github.libretube.ui.sheets
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.constants.IntentData
|
import com.github.libretube.constants.IntentData
|
||||||
import com.github.libretube.databinding.DialogAddChannelToGroupBinding
|
import com.github.libretube.databinding.DialogAddChannelToGroupBinding
|
||||||
@ -16,6 +15,10 @@ import kotlinx.coroutines.withContext
|
|||||||
class AddChannelToGroupSheet : ExpandedBottomSheet(R.layout.dialog_add_channel_to_group) {
|
class AddChannelToGroupSheet : ExpandedBottomSheet(R.layout.dialog_add_channel_to_group) {
|
||||||
private lateinit var channelId: String
|
private lateinit var channelId: String
|
||||||
|
|
||||||
|
private val addToGroupAdapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
AddChannelToGroupAdapter(channelId)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@ -26,7 +29,8 @@ class AddChannelToGroupSheet : ExpandedBottomSheet(R.layout.dialog_add_channel_t
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val binding = DialogAddChannelToGroupBinding.bind(view)
|
val binding = DialogAddChannelToGroupBinding.bind(view)
|
||||||
|
|
||||||
binding.groupsRV.layoutManager = LinearLayoutManager(context)
|
binding.groupsRV.adapter = addToGroupAdapter
|
||||||
|
|
||||||
binding.cancel.setOnClickListener {
|
binding.cancel.setOnClickListener {
|
||||||
requireDialog().dismiss()
|
requireDialog().dismiss()
|
||||||
}
|
}
|
||||||
@ -36,7 +40,7 @@ class AddChannelToGroupSheet : ExpandedBottomSheet(R.layout.dialog_add_channel_t
|
|||||||
val subscriptionGroups = subGroupsDao.getAll().sortedBy { it.index }.toMutableList()
|
val subscriptionGroups = subGroupsDao.getAll().sortedBy { it.index }.toMutableList()
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
binding.groupsRV.adapter = AddChannelToGroupAdapter(subscriptionGroups, channelId)
|
addToGroupAdapter.submitList(subscriptionGroups)
|
||||||
|
|
||||||
binding.okay.setOnClickListener {
|
binding.okay.setOnClickListener {
|
||||||
requireDialog().hide()
|
requireDialog().hide()
|
||||||
|
@ -30,8 +30,16 @@ class EditChannelGroupSheet : ExpandedBottomSheet(R.layout.dialog_edit_channel_g
|
|||||||
private val channelGroupsModel: EditChannelGroupsModel by activityViewModels()
|
private val channelGroupsModel: EditChannelGroupsModel by activityViewModels()
|
||||||
private var channels = listOf<Subscription>()
|
private var channels = listOf<Subscription>()
|
||||||
|
|
||||||
|
private val channelsAdapter = SubscriptionGroupChannelsAdapter(
|
||||||
|
channelGroupsModel.groupToEdit!!
|
||||||
|
) {
|
||||||
|
channelGroupsModel.groupToEdit = it
|
||||||
|
updateConfirmStatus()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
_binding = DialogEditChannelGroupBinding.bind(view)
|
_binding = DialogEditChannelGroupBinding.bind(view)
|
||||||
|
binding.channelsRV.adapter = channelsAdapter
|
||||||
binding.groupName.setText(channelGroupsModel.groupToEdit?.name)
|
binding.groupName.setText(channelGroupsModel.groupToEdit?.name)
|
||||||
val oldGroupName = channelGroupsModel.groupToEdit?.name.orEmpty()
|
val oldGroupName = channelGroupsModel.groupToEdit?.name.orEmpty()
|
||||||
|
|
||||||
@ -97,15 +105,12 @@ class EditChannelGroupSheet : ExpandedBottomSheet(R.layout.dialog_edit_channel_g
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showChannels(channels: List<Subscription>, query: String?) {
|
private fun showChannels(channels: List<Subscription>, query: String?) {
|
||||||
binding.channelsRV.adapter = SubscriptionGroupChannelsAdapter(
|
|
||||||
channels.filter { query == null || it.name.lowercase().contains(query.lowercase()) },
|
|
||||||
channelGroupsModel.groupToEdit!!
|
|
||||||
) {
|
|
||||||
channelGroupsModel.groupToEdit = it
|
|
||||||
updateConfirmStatus()
|
|
||||||
}
|
|
||||||
binding.subscriptionsContainer.isVisible = true
|
binding.subscriptionsContainer.isVisible = true
|
||||||
binding.progress.isVisible = false
|
binding.progress.isVisible = false
|
||||||
|
|
||||||
|
channelsAdapter.submitList(
|
||||||
|
channels.filter { query == null || it.name.lowercase().contains(query.lowercase()) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateConfirmStatus() {
|
private fun updateConfirmStatus() {
|
||||||
|
@ -10,7 +10,6 @@ import androidx.core.text.method.LinkMovementMethodCompat
|
|||||||
import androidx.core.text.parseAsHtml
|
import androidx.core.text.parseAsHtml
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.obj.Segment
|
import com.github.libretube.api.obj.Segment
|
||||||
import com.github.libretube.api.obj.Streams
|
import com.github.libretube.api.obj.Streams
|
||||||
@ -33,6 +32,8 @@ class DescriptionLayout(
|
|||||||
private var streams: Streams? = null
|
private var streams: Streams? = null
|
||||||
var handleLink: (link: String) -> Unit = {}
|
var handleLink: (link: String) -> Unit = {}
|
||||||
|
|
||||||
|
private val videoTagsAdapter = VideoTagsAdapter()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
binding.playerTitleLayout.setOnClickListener {
|
binding.playerTitleLayout.setOnClickListener {
|
||||||
toggleDescription()
|
toggleDescription()
|
||||||
@ -41,6 +42,8 @@ class DescriptionLayout(
|
|||||||
streams?.title?.let { ClipboardHelper.save(context, text = it) }
|
streams?.title?.let { ClipboardHelper.save(context, text = it) }
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.tagsRecycler.adapter = videoTagsAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSegments(segments: List<Segment>) {
|
fun setSegments(segments: List<Segment>) {
|
||||||
@ -101,9 +104,7 @@ class DescriptionLayout(
|
|||||||
"${context?.getString(R.string.visibility)}: $visibility"
|
"${context?.getString(R.string.visibility)}: $visibility"
|
||||||
|
|
||||||
if (streams.tags.isNotEmpty()) {
|
if (streams.tags.isNotEmpty()) {
|
||||||
binding.tagsRecycler.layoutManager =
|
videoTagsAdapter.submitList(streams.tags)
|
||||||
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
|
||||||
binding.tagsRecycler.adapter = VideoTagsAdapter(streams.tags)
|
|
||||||
}
|
}
|
||||||
binding.tagsRecycler.isVisible = streams.tags.isNotEmpty()
|
binding.tagsRecycler.isVisible = streams.tags.isNotEmpty()
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:checkable="false"
|
android:checkable="false"
|
||||||
android:visibility="gone"
|
android:text="Sponsor"
|
||||||
android:text="Sponsor"/>
|
android:visibility="gone" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@ -133,7 +133,9 @@
|
|||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/tags_recycler"
|
android:id="@+id/tags_recycler"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@ -48,7 +49,9 @@
|
|||||||
android:id="@+id/groupsRV"
|
android:id="@+id/groupsRV"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/add_channel_to_group_row" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@ -35,10 +36,12 @@
|
|||||||
android:id="@+id/featuredRV"
|
android:id="@+id/featuredRV"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingHorizontal="10dp"
|
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:nestedScrollingEnabled="false"
|
android:nestedScrollingEnabled="false"
|
||||||
android:visibility="gone" />
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/watchingTV"
|
android:id="@+id/watchingTV"
|
||||||
@ -49,10 +52,12 @@
|
|||||||
android:id="@+id/watchingRV"
|
android:id="@+id/watchingRV"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingHorizontal="10dp"
|
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:nestedScrollingEnabled="false"
|
android:nestedScrollingEnabled="false"
|
||||||
android:visibility="gone" />
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/trendingTV"
|
android:id="@+id/trendingTV"
|
||||||
@ -70,7 +75,9 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:nestedScrollingEnabled="false"
|
android:nestedScrollingEnabled="false"
|
||||||
android:visibility="gone" />
|
android:visibility="gone"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
app:spanCount="2" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
@ -84,7 +91,9 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:nestedScrollingEnabled="false"
|
android:nestedScrollingEnabled="false"
|
||||||
android:visibility="gone" />
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/playlistsTV"
|
android:id="@+id/playlistsTV"
|
||||||
@ -96,7 +105,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:nestedScrollingEnabled="false"
|
android:nestedScrollingEnabled="false"
|
||||||
android:visibility="gone" />
|
android:visibility="gone"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@ -134,8 +144,8 @@
|
|||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_marginTop="30dp"
|
android:layout_marginTop="30dp"
|
||||||
android:textSize="12sp"
|
android:text="@string/retry"
|
||||||
android:text="@string/retry"/>
|
android:textSize="12sp" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/change_instance"
|
android:id="@+id/change_instance"
|
||||||
@ -144,8 +154,8 @@
|
|||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:textSize="12sp"
|
android:text="@string/change_instance"
|
||||||
android:text="@string/change_instance"/>
|
android:textSize="12sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@ -9,7 +10,10 @@
|
|||||||
android:id="@+id/suggestions_recycler"
|
android:id="@+id/suggestions_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginVertical="10dp" />
|
android:layout_marginVertical="10dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:reverseLayout="true"
|
||||||
|
app:stackFromEnd="true" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/history_empty"
|
android:id="@+id/history_empty"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user