diff --git a/app/src/main/java/com/github/libretube/ui/activities/VideoTagsAdapter.kt b/app/src/main/java/com/github/libretube/ui/activities/VideoTagsAdapter.kt index bb8ff28e7..a8f83e39b 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/VideoTagsAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/VideoTagsAdapter.kt @@ -2,21 +2,29 @@ package com.github.libretube.ui.activities import android.view.LayoutInflater 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.ui.viewholders.VideoTagsViewHolder -class VideoTagsAdapter(private val tags: List) : - RecyclerView.Adapter() { +class VideoTagsAdapter : + ListAdapter(object : DiffUtil.ItemCallback() { + 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 { val binding = VideoTagRowBinding.inflate(LayoutInflater.from(parent.context)) return VideoTagsViewHolder(binding) } - override fun getItemCount() = tags.size - override fun onBindViewHolder(holder: VideoTagsViewHolder, position: Int) { - val tag = tags[position] + val tag = getItem(holder.bindingAdapterPosition) holder.binding.apply { tagText.text = tag root.setOnClickListener { diff --git a/app/src/main/java/com/github/libretube/ui/activities/WelcomeActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/WelcomeActivity.kt index 0f8f5f2fd..03e79bb6d 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/WelcomeActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/WelcomeActivity.kt @@ -47,13 +47,16 @@ class WelcomeActivity : BaseActivity() { val binding = ActivityWelcomeBinding.inflate(layoutInflater) 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) viewModel.instances.observe(this) { instances -> - binding.instancesRecycler.layoutManager = LinearLayoutManager(this@WelcomeActivity) - binding.instancesRecycler.adapter = InstancesAdapter(ImmutableList.copyOf(instances), viewModel.selectedInstanceIndex.value) { index -> - viewModel.selectedInstanceIndex.value = index - binding.okay.alpha = 1f - } + adapter.submitList(ImmutableList.copyOf(instances)) binding.progress.isGone = true } viewModel.fetchInstances() diff --git a/app/src/main/java/com/github/libretube/ui/adapters/AddChannelToGroupAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/AddChannelToGroupAdapter.kt index 6b4b7c6bb..cf17828a3 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/AddChannelToGroupAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/AddChannelToGroupAdapter.kt @@ -2,25 +2,35 @@ package com.github.libretube.ui.adapters import android.view.LayoutInflater 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.db.obj.SubscriptionGroup import com.github.libretube.ui.viewholders.AddChannelToGroupViewHolder class AddChannelToGroupAdapter( - private val channelGroups: MutableList, private val channelId: String -) : RecyclerView.Adapter() { +) : ListAdapter(object: DiffUtil.ItemCallback() { + 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 { val layoutInflater = LayoutInflater.from(parent.context) val binding = AddChannelToGroupRowBinding.inflate(layoutInflater, parent, false) return AddChannelToGroupViewHolder(binding) } - override fun getItemCount() = channelGroups.size - override fun onBindViewHolder(holder: AddChannelToGroupViewHolder, position: Int) { - val channelGroup = channelGroups[position] + val channelGroup = getItem(holder.bindingAdapterPosition) holder.binding.apply { groupName.text = channelGroup.name diff --git a/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt index 1556017cd..d7324589b 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/DownloadsAdapter.kt @@ -9,7 +9,8 @@ import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.core.view.isGone 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.constants.IntentData import com.github.libretube.databinding.VideoRowBinding @@ -37,9 +38,21 @@ import kotlin.io.path.fileSize class DownloadsAdapter( private val context: Context, private val downloadTab: DownloadTab, - private val downloads: MutableList, private val toggleDownload: (DownloadWithItems) -> Boolean -) : RecyclerView.Adapter() { +) : ListAdapter(object : + DiffUtil.ItemCallback() { + 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 { val binding = VideoRowBinding.inflate( LayoutInflater.from(parent.context), @@ -51,8 +64,8 @@ class DownloadsAdapter( @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: DownloadsViewHolder, position: Int) { - val download = downloads[position].download - val items = downloads[position].downloadItems + val download = getItem(holder.bindingAdapterPosition).download + val items = getItem(holder.bindingAdapterPosition).downloadItems holder.binding.apply { fileSize.isVisible = true @@ -96,7 +109,7 @@ class DownloadsAdapter( } progressBar.setOnClickListener { - val isDownloading = toggleDownload(downloads[position]) + val isDownloading = toggleDownload(getItem(holder.bindingAdapterPosition)) resumePauseBtn.setImageResource( if (isDownloading) { @@ -113,7 +126,11 @@ class DownloadsAdapter( intent.putExtra(IntentData.videoId, download.videoId) root.context.startActivity(intent) } else { - BackgroundHelper.playOnBackgroundOffline(root.context, download.videoId, downloadTab) + BackgroundHelper.playOnBackgroundOffline( + root.context, + download.videoId, + downloadTab + ) NavigationHelper.openAudioPlayerFragment(root.context, offlinePlayer = true) } } @@ -152,11 +169,9 @@ class DownloadsAdapter( .show() } - fun itemAt(index: Int) = downloads[index] - fun deleteDownload(position: Int) { - val download = downloads[position].download - val items = downloads[position].downloadItems + val download = getItem(position).download + val items = getItem(position).downloadItems items.forEach { it.path.deleteIfExists() @@ -168,9 +183,9 @@ class DownloadsAdapter( runBlocking(Dispatchers.IO) { DatabaseHolder.Database.downloadDao().deleteDownload(download) } - downloads.removeAt(position) - notifyItemRemoved(position) - notifyItemRangeChanged(position, itemCount) + submitList(currentList.toMutableList().also { + it.removeAt(position) + }) } fun restoreItem(position: Int) { @@ -178,6 +193,4 @@ class DownloadsAdapter( notifyItemRemoved(position) notifyItemInserted(position) } - - override fun getItemCount() = downloads.size } diff --git a/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt index ead0c865f..2327b0329 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/InstancesAdapter.kt @@ -3,18 +3,26 @@ package com.github.libretube.ui.adapters import android.annotation.SuppressLint import android.view.LayoutInflater 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.api.obj.PipedInstance import com.github.libretube.databinding.InstanceRowBinding import com.github.libretube.ui.viewholders.InstancesViewHolder -import com.google.common.collect.ImmutableList class InstancesAdapter( - private val instances: ImmutableList, initialSelectionApiIndex: Int?, private val onSelectInstance: (index: Int) -> Unit -) : RecyclerView.Adapter() { +) : ListAdapter(object: DiffUtil.ItemCallback() { + 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 } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InstancesViewHolder { @@ -23,11 +31,9 @@ class InstancesAdapter( return InstancesViewHolder(binding) } - override fun getItemCount() = instances.size - @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: InstancesViewHolder, position: Int) { - val instance = instances[position] + val instance = getItem(holder.bindingAdapterPosition) holder.binding.apply { var instanceText = "${instance.name} ${instance.locations}" diff --git a/app/src/main/java/com/github/libretube/ui/adapters/LegacySubscriptionAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/LegacySubscriptionAdapter.kt index 156851fea..2514de1a4 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/LegacySubscriptionAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/LegacySubscriptionAdapter.kt @@ -3,7 +3,9 @@ package com.github.libretube.ui.adapters import android.view.LayoutInflater import android.view.ViewGroup 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.databinding.LegacySubscriptionChannelBinding 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.viewholders.LegacySubscriptionViewHolder -class LegacySubscriptionAdapter( - private val subscriptions: List -) : RecyclerView.Adapter() { +class LegacySubscriptionAdapter : ListAdapter(object : + DiffUtil.ItemCallback() { + 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( parent: ViewGroup, @@ -27,7 +37,7 @@ class LegacySubscriptionAdapter( } override fun onBindViewHolder(holder: LegacySubscriptionViewHolder, position: Int) { - val subscription = subscriptions[position] + val subscription = getItem(holder.bindingAdapterPosition) holder.binding.apply { channelName.text = subscription.name ImageHelper.loadImage( @@ -51,6 +61,4 @@ class LegacySubscriptionAdapter( } } } - - override fun getItemCount() = subscriptions.size } diff --git a/app/src/main/java/com/github/libretube/ui/adapters/PlaylistBookmarkAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/PlaylistBookmarkAdapter.kt index 8ebcf42df..c4a85788e 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/PlaylistBookmarkAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/PlaylistBookmarkAdapter.kt @@ -5,7 +5,8 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.os.bundleOf 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.constants.IntentData import com.github.libretube.databinding.PlaylistBookmarkRowBinding @@ -23,9 +24,17 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class PlaylistBookmarkAdapter( - private val bookmarks: List, private val bookmarkMode: BookmarkMode = BookmarkMode.FRAGMENT -) : RecyclerView.Adapter() { +) : ListAdapter(object: DiffUtil.ItemCallback() { + 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 { val layoutInflater = LayoutInflater.from(parent.context) return when (bookmarkMode) { @@ -39,8 +48,6 @@ class PlaylistBookmarkAdapter( } } - override fun getItemCount() = bookmarks.size - private fun showPlaylistOptions(context: Context, bookmark: PlaylistBookmark) { val sheet = PlaylistOptionsBottomSheet() sheet.arguments = bundleOf( @@ -54,7 +61,7 @@ class PlaylistBookmarkAdapter( } override fun onBindViewHolder(holder: PlaylistBookmarkViewHolder, position: Int) { - val bookmark = bookmarks[position] + val bookmark = getItem(holder.bindingAdapterPosition) holder.playlistBookmarkBinding?.apply { ImageHelper.loadImage(bookmark.thumbnailUrl, thumbnail) playlistName.text = bookmark.playlistName diff --git a/app/src/main/java/com/github/libretube/ui/adapters/PlaylistsAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/PlaylistsAdapter.kt index 6157b97e6..2dc17061f 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/PlaylistsAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/PlaylistsAdapter.kt @@ -3,7 +3,8 @@ package com.github.libretube.ui.adapters import android.view.LayoutInflater import android.view.ViewGroup 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.api.obj.Playlists 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 class PlaylistsAdapter( - private val playlists: MutableList, private val playlistType: PlaylistType -) : RecyclerView.Adapter() { - - override fun getItemCount() = playlists.size - - fun updateItems(newItems: List) { - val oldSize = playlists.size - playlists.addAll(newItems) - notifyItemRangeInserted(oldSize, playlists.size) +) : ListAdapter(object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Playlists, newItem: Playlists): Boolean { + return oldItem.id == newItem.id } + override fun areContentsTheSame(oldItem: Playlists, newItem: Playlists): Boolean { + return oldItem == newItem + } + +}) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaylistsViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val binding = PlaylistsRowBinding.inflate(layoutInflater, parent, false) @@ -36,7 +37,7 @@ class PlaylistsAdapter( } override fun onBindViewHolder(holder: PlaylistsViewHolder, position: Int) { - val playlist = playlists[position] + val playlist = getItem(holder.bindingAdapterPosition) holder.binding.apply { // set imageview drawable as empty playlist if imageview empty if (playlist.thumbnail.orEmpty().split("/").size <= 4) { @@ -80,7 +81,7 @@ class PlaylistsAdapter( if (isPlaylistToBeDeleted) { // 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) { - playlists.removeAt(position) - activity.runOnUiThread { - notifyItemRemoved(position) - notifyItemRangeChanged(position, itemCount) + private fun onDelete(position: Int) { + val newList = currentList.toMutableList().also { + it.removeAt(position) } + submitList(newList) } } diff --git a/app/src/main/java/com/github/libretube/ui/adapters/SearchHistoryAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/SearchHistoryAdapter.kt index cf2097bce..79f0360c6 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/SearchHistoryAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/SearchHistoryAdapter.kt @@ -2,9 +2,9 @@ package com.github.libretube.ui.adapters import android.view.LayoutInflater import android.view.ViewGroup -import androidx.appcompat.widget.SearchView 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.db.DatabaseHolder.Database import com.github.libretube.db.obj.SearchHistoryItem @@ -13,13 +13,18 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking class SearchHistoryAdapter( - private var historyList: List, - private val searchView: SearchView -) : - RecyclerView.Adapter() { + private val onRootClickListener: (String) -> Unit, + private val onArrowClickListener: (String) -> Unit, +) : ListAdapter(object: DiffUtil.ItemCallback() { + 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 { val layoutInflater = LayoutInflater.from(parent.context) val binding = SuggestionRowBinding.inflate(layoutInflater, parent, false) @@ -27,26 +32,28 @@ class SearchHistoryAdapter( } override fun onBindViewHolder(holder: SuggestionsViewHolder, position: Int) { - val historyQuery = historyList[position] + val historyQuery = getItem(holder.bindingAdapterPosition) holder.binding.apply { suggestionText.text = historyQuery deleteHistory.isVisible = true deleteHistory.setOnClickListener { - historyList -= historyQuery + val updatedList = currentList.toMutableList().also { + it.remove(historyQuery) + } runBlocking(Dispatchers.IO) { Database.searchHistoryDao().delete(SearchHistoryItem(historyQuery)) } - notifyItemRemoved(position) - notifyItemRangeChanged(position, itemCount) + + submitList(updatedList) } root.setOnClickListener { - searchView.setQuery(historyQuery, true) + onRootClickListener(historyQuery) } arrow.setOnClickListener { - searchView.setQuery(historyQuery, false) + onArrowClickListener(historyQuery) } } } diff --git a/app/src/main/java/com/github/libretube/ui/adapters/SearchSuggestionsAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/SearchSuggestionsAdapter.kt index 1cf707125..6466155ce 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/SearchSuggestionsAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/SearchSuggestionsAdapter.kt @@ -2,18 +2,24 @@ package com.github.libretube.ui.adapters import android.view.LayoutInflater import android.view.ViewGroup -import androidx.appcompat.widget.SearchView -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.ui.viewholders.SuggestionsViewHolder class SearchSuggestionsAdapter( - private var suggestionsList: List, - private val searchView: SearchView -) : - RecyclerView.Adapter() { + private val onRootClickListener: (String) -> Unit, + private val onArrowClickListener: (String) -> Unit, +) : ListAdapter(object: DiffUtil.ItemCallback() { + 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 { val layoutInflater = LayoutInflater.from(parent.context) @@ -22,14 +28,14 @@ class SearchSuggestionsAdapter( } override fun onBindViewHolder(holder: SuggestionsViewHolder, position: Int) { - val suggestion = suggestionsList[position] + val suggestion = getItem(holder.bindingAdapterPosition) holder.binding.apply { suggestionText.text = suggestion root.setOnClickListener { - searchView.setQuery(suggestion, true) + onRootClickListener(suggestion) } arrow.setOnClickListener { - searchView.setQuery(suggestion, false) + onArrowClickListener(suggestion) } } } diff --git a/app/src/main/java/com/github/libretube/ui/adapters/SubscriptionChannelAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/SubscriptionChannelAdapter.kt index c4ce94ab3..460d582dd 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/SubscriptionChannelAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/SubscriptionChannelAdapter.kt @@ -3,7 +3,8 @@ package com.github.libretube.ui.adapters import android.view.LayoutInflater import android.view.ViewGroup 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.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.viewholders.SubscriptionChannelViewHolder -class SubscriptionChannelAdapter( - private val subscriptions: MutableList -) : RecyclerView.Adapter() { +class SubscriptionChannelAdapter : ListAdapter(object : + DiffUtil.ItemCallback() { + 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 - override fun getItemCount() = minOf(visibleCount, subscriptions.size) + override fun getItemCount() = minOf(visibleCount, currentList.size) override fun onCreateViewHolder( parent: ViewGroup, @@ -33,13 +42,13 @@ class SubscriptionChannelAdapter( fun updateItems() { val oldSize = visibleCount - visibleCount += minOf(10, subscriptions.size - oldSize) + visibleCount += minOf(10, currentList.size - oldSize) if (visibleCount == oldSize) return notifyItemRangeInserted(oldSize, visibleCount) } override fun onBindViewHolder(holder: SubscriptionChannelViewHolder, position: Int) { - val subscription = subscriptions[position] + val subscription = getItem(holder.bindingAdapterPosition) holder.binding.apply { subscriptionChannelName.text = subscription.name diff --git a/app/src/main/java/com/github/libretube/ui/adapters/SubscriptionGroupChannelsAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/SubscriptionGroupChannelsAdapter.kt index b8e04c373..664b91abb 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/SubscriptionGroupChannelsAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/SubscriptionGroupChannelsAdapter.kt @@ -2,7 +2,8 @@ package com.github.libretube.ui.adapters import android.view.LayoutInflater 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.databinding.SubscriptionGroupChannelRowBinding import com.github.libretube.db.obj.SubscriptionGroup @@ -12,10 +13,18 @@ import com.github.libretube.helpers.NavigationHelper import com.github.libretube.ui.viewholders.SubscriptionGroupChannelRowViewHolder class SubscriptionGroupChannelsAdapter( - private val channels: List, private val group: SubscriptionGroup, private val onGroupChanged: (SubscriptionGroup) -> Unit -) : RecyclerView.Adapter() { +) : ListAdapter(object: DiffUtil.ItemCallback() { + 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( parent: ViewGroup, viewType: Int @@ -25,10 +34,8 @@ class SubscriptionGroupChannelsAdapter( return SubscriptionGroupChannelRowViewHolder(binding) } - override fun getItemCount() = channels.size - override fun onBindViewHolder(holder: SubscriptionGroupChannelRowViewHolder, position: Int) { - val channel = channels[position] + val channel = getItem(holder.bindingAdapterPosition) holder.binding.apply { root.setOnClickListener { NavigationHelper.navigateChannel(root.context, channel.url) diff --git a/app/src/main/java/com/github/libretube/ui/adapters/VideosAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/VideosAdapter.kt index e9d819752..662c007db 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/VideosAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/VideosAdapter.kt @@ -8,8 +8,9 @@ import androidx.core.os.bundleOf import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView.LayoutManager import com.github.libretube.api.obj.StreamItem import com.github.libretube.constants.IntentData @@ -36,29 +37,38 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class VideosAdapter( - private val streamItems: MutableList, private val forceMode: LayoutMode = LayoutMode.RESPECT_PREF -) : RecyclerView.Adapter() { - override fun getItemCount() = streamItems.size +) : ListAdapter(object: DiffUtil.ItemCallback() { + 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 { - 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) { - val feedSize = streamItems.size - streamItems.addAll(newItems) - notifyItemRangeInserted(feedSize, newItems.size) + val updatedList = currentList.toMutableList().also { + it.addAll(newItems) + } + + submitList(updatedList) } fun removeItemById(videoId: String) { - val index = streamItems.indexOfFirst { + val index = currentList.indexOfFirst { it.url?.toID() == videoId }.takeIf { it > 0 } ?: return - streamItems.removeAt(index) + val updatedList = currentList.toMutableList().also { + it.removeAt(index) + } - notifyItemRemoved(index) - notifyItemRangeChanged(index, itemCount) + submitList(updatedList) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideosViewHolder { @@ -90,7 +100,7 @@ class VideosAdapter( @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: VideosViewHolder, position: Int) { - val video = streamItems[position] + val video = getItem(holder.bindingAdapterPosition) val videoId = video.url.orEmpty().toID() val context = ( diff --git a/app/src/main/java/com/github/libretube/ui/adapters/WatchHistoryAdapter.kt b/app/src/main/java/com/github/libretube/ui/adapters/WatchHistoryAdapter.kt index 2191c5627..3796dde6f 100644 --- a/app/src/main/java/com/github/libretube/ui/adapters/WatchHistoryAdapter.kt +++ b/app/src/main/java/com/github/libretube/ui/adapters/WatchHistoryAdapter.kt @@ -5,7 +5,8 @@ import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.core.view.isGone 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.databinding.VideoRowBinding import com.github.libretube.db.DatabaseHolder @@ -24,27 +25,34 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -class WatchHistoryAdapter( - private val watchHistory: MutableList -) : - RecyclerView.Adapter() { +class WatchHistoryAdapter : ListAdapter(object : + DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: WatchHistoryItem, newItem: WatchHistoryItem): Boolean { + return oldItem == newItem + } - override fun getItemCount() = watchHistory.size + override fun areContentsTheSame(oldItem: WatchHistoryItem, newItem: WatchHistoryItem): Boolean { + return oldItem == newItem + } + +}) { fun removeFromWatchHistory(position: Int) { - val history = watchHistory[position] + val history = getItem(position) runBlocking(Dispatchers.IO) { DatabaseHolder.Database.watchHistoryDao().delete(history) } - watchHistory.removeAt(position) - notifyItemRemoved(position) - notifyItemRangeChanged(position, itemCount) + val updatedList = currentList.toMutableList().also { + it.removeAt(position) + } + submitList(updatedList) } fun insertItems(items: List) { - val oldSize = itemCount - this.watchHistory.addAll(items) - notifyItemRangeInserted(oldSize, itemCount) + val updatedList = currentList.toMutableList().also { + it.addAll(items) + } + submitList(updatedList) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WatchHistoryViewHolder { @@ -54,7 +62,7 @@ class WatchHistoryAdapter( } override fun onBindViewHolder(holder: WatchHistoryViewHolder, position: Int) { - val video = watchHistory[position] + val video = getItem(holder.bindingAdapterPosition) holder.binding.apply { videoTitle.text = video.title channelName.text = video.uploader diff --git a/app/src/main/java/com/github/libretube/ui/fragments/ChannelContentFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/ChannelContentFragment.kt index 49998e20a..ff753aa37 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/ChannelContentFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/ChannelContentFragment.kt @@ -87,10 +87,8 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch } nextPage = response.nextpage - val binding = _binding ?: return@launch - searchChannelAdapter = SearchChannelAdapter() - binding.channelRecView.adapter = searchChannelAdapter searchChannelAdapter?.submitList(response.content) + val binding = _binding ?: return@launch binding.progressBar.isGone = true isLoading = false @@ -123,6 +121,9 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch channelId = arguments.getString(IntentData.channelId) nextPage = arguments.getString(IntentData.nextPage) + searchChannelAdapter = SearchChannelAdapter() + binding.channelRecView.adapter = searchChannelAdapter + binding.channelRecView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) @@ -147,9 +148,10 @@ class ChannelContentFragment : DynamicLayoutManagerFragment(R.layout.fragment_ch if (tabData?.data.isNullOrEmpty()) { channelAdapter = VideosAdapter( - arguments.parcelableArrayList(IntentData.videoList)!!, forceMode = VideosAdapter.Companion.LayoutMode.CHANNEL_ROW - ) + ).also { + it.submitList(arguments.parcelableArrayList(IntentData.videoList)!!) + } binding.channelRecView.adapter = channelAdapter binding.progressBar.isGone = true diff --git a/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt index 2c41901f8..d9b0d8b2d 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/ChannelFragment.kt @@ -236,9 +236,10 @@ class ChannelFragment : DynamicLayoutManagerFragment(R.layout.fragment_channel) }.attach() channelAdapter = VideosAdapter( - response.relatedStreams.toMutableList(), forceMode = VideosAdapter.Companion.LayoutMode.CHANNEL_ROW - ) + ).also { + it.submitList(response.relatedStreams) + } tabList.clear() val tabs = listOf(ChannelTab(VIDEOS_TAB_KEY, "")) + response.tabs diff --git a/app/src/main/java/com/github/libretube/ui/fragments/DownloadsFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/DownloadsFragment.kt index 46f39619f..a173dabee 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/DownloadsFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/DownloadsFragment.kt @@ -146,6 +146,32 @@ class DownloadsFragmentPage : DynamicLayoutManagerFragment(R.layout.fragment_dow override fun onViewCreated(view: View, savedInstanceState: Bundle?) { _binding = FragmentDownloadContentBinding.bind(view) 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 = PreferenceHelper.getInt(PreferenceKeys.SELECTED_DOWNLOAD_SORT_TYPE, 0) @@ -156,7 +182,6 @@ class DownloadsFragmentPage : DynamicLayoutManagerFragment(R.layout.fragment_dow binding.sortType.text = filterOptions[index] if (::adapter.isInitialized) { sortDownloadList(index, selectedSortType) - adapter.notifyDataSetChanged() } selectedSortType = index PreferenceHelper.putInt( @@ -178,32 +203,6 @@ class DownloadsFragmentPage : DynamicLayoutManagerFragment(R.layout.fragment_dow 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 -> adapter.showDeleteDialog(requireContext(), position) @@ -251,10 +250,10 @@ class DownloadsFragmentPage : DynamicLayoutManagerFragment(R.layout.fragment_dow private fun sortDownloadList(sortType: Int, previousSortType: Int? = null) { if (previousSortType == null && sortType == 1) { - downloads.reverse() + adapter.submitList(downloads.reversed()) } 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) { _, _ -> lifecycleScope.launch { 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)) { adapter.deleteDownload(downloadIndex) } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt index c63fb5742..520ffff4c 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/HomeFragment.kt @@ -8,9 +8,6 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels 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 com.github.libretube.R 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 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?) { _binding = FragmentHomeBinding.bind(view) 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) { trending.observe(viewLifecycleOwner, ::showTrending) feed.observe(viewLifecycleOwner, ::showFeed) @@ -123,11 +142,7 @@ class HomeFragment : Fragment(R.layout.fragment_home) { if (streamItems == null) return makeVisible(binding.trendingRV, binding.trendingTV) - binding.trendingRV.layoutManager = GridLayoutManager(context, 2) - binding.trendingRV.adapter = VideosAdapter( - streamItems.toMutableList(), - forceMode = LayoutMode.TRENDING_ROW - ) + trendingAdapter.submitList(streamItems) } private fun showFeed(streamItems: List?) { @@ -138,57 +153,29 @@ class HomeFragment : Fragment(R.layout.fragment_home) { val feedVideos = streamItems .let { DatabaseHelper.filterByStatusAndWatchPosition(it, hideWatched) } .take(20) - .toMutableList() - with(binding.featuredRV) { - layoutManager = LinearLayoutManager(context, HORIZONTAL, false) - adapter = VideosAdapter(feedVideos, forceMode = LayoutMode.RELATED_COLUMN) - } + feedAdapter.submitList(feedVideos) } private fun showBookmarks(bookmarks: List?) { if (bookmarks == null) return makeVisible(binding.bookmarksTV, binding.bookmarksRV) - with(binding.bookmarksRV) { - layoutManager = LinearLayoutManager(context, HORIZONTAL, false) - adapter = PlaylistBookmarkAdapter( - bookmarks.toMutableList(), - PlaylistBookmarkAdapter.Companion.BookmarkMode.HOME - ) - } + bookmarkAdapter.submitList(bookmarks) } private fun showPlaylists(playlists: List?) { if (playlists == null) return makeVisible(binding.playlistsRV, binding.playlistsTV) - binding.playlistsRV.layoutManager = LinearLayoutManager(context) - 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 - } - } - }) + playlistAdapter.submitList(playlists) } private fun showContinueWatching(unwatchedVideos: List?) { if (unwatchedVideos == null) return makeVisible(binding.watchingRV, binding.watchingTV) - binding.watchingRV.layoutManager = LinearLayoutManager(context, HORIZONTAL, false) - binding.watchingRV.adapter = VideosAdapter( - unwatchedVideos.toMutableList(), - forceMode = LayoutMode.RELATED_COLUMN - ) + watchingAdapter.submitList(unwatchedVideos) } private fun updateLoading(isLoading: Boolean) { diff --git a/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt index bcc1509af..eff2fbece 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/LibraryFragment.kt @@ -45,6 +45,9 @@ class LibraryFragment : DynamicLayoutManagerFragment(R.layout.fragment_library) private val commonPlayerViewModel: CommonPlayerViewModel by activityViewModels() + private val playlistsAdapter = PlaylistsAdapter(PlaylistsHelper.getPrivatePlaylistType()) + private val playlistBookmarkAdapter = PlaylistBookmarkAdapter() + override fun setLayoutManagers(gridItems: Int) { _binding?.bookmarksRecView?.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) 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 commonPlayerViewModel.isMiniPlayerVisible.observe(viewLifecycleOwner) { updateFABMargin(it) @@ -142,7 +157,7 @@ class LibraryFragment : DynamicLayoutManagerFragment(R.layout.fragment_library) binding.bookmarksCV.isVisible = 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) { 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.sortTV.isVisible = true - binding.playlistRecView.adapter = playlistsAdapter + playlistsAdapter.submitList(playlists) } } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index d18a5e46b..3e152fbb6 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -1128,13 +1128,14 @@ class PlayerFragment : Fragment(R.layout.fragment_player), OnlinePlayerOptions { if (PlayerHelper.relatedStreamsEnabled) { val relatedLayoutManager = binding.relatedRecView.layoutManager as LinearLayoutManager binding.relatedRecView.adapter = VideosAdapter( - streams.relatedStreams.filter { !it.title.isNullOrBlank() }.toMutableList(), forceMode = if (relatedLayoutManager.orientation == LinearLayoutManager.HORIZONTAL) { VideosAdapter.Companion.LayoutMode.RELATED_COLUMN } else { VideosAdapter.Companion.LayoutMode.TRENDING_ROW } - ) + ).also { adapter -> + adapter.submitList(streams.relatedStreams.filter { !it.title.isNullOrBlank() }) + } } // update the subscribed state diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt index 28c791f36..93cf97014 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlaylistFragment.kt @@ -325,6 +325,7 @@ class PlaylistFragment : DynamicLayoutManagerFragment(R.layout.fragment_playlist playlistId, playlistType ) + // TODO make sure the adapter is set once in onViewCreated binding.playlistRecView.adapter = playlistAdapter // listen for playlist items to become deleted diff --git a/app/src/main/java/com/github/libretube/ui/fragments/SearchSuggestionsFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/SearchSuggestionsFragment.kt index 4f9080eaa..0bcf0cce1 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/SearchSuggestionsFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/SearchSuggestionsFragment.kt @@ -7,9 +7,10 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.map import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearLayoutManager import com.github.libretube.R import com.github.libretube.api.RetrofitInstance 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 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?) { super.onCreate(savedInstanceState) viewModel.searchQuery.value = arguments?.getString(IntentData.query) @@ -43,9 +65,15 @@ class SearchSuggestionsFragment : Fragment(R.layout.fragment_search_suggestions) _binding = FragmentSearchSuggestionsBinding.bind(view) super.onViewCreated(view, savedInstanceState) - binding.suggestionsRecycler.layoutManager = LinearLayoutManager(requireContext()).apply { - reverseLayout = true - stackFromEnd = true + viewModel.searchQuery + .map { it.isNullOrEmpty() } + .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 @@ -81,30 +109,19 @@ class SearchSuggestionsFragment : Fragment(R.layout.fragment_search_suggestions) return@launch } // only load the suggestions if the input field didn't get cleared yet - val suggestionsAdapter = SearchSuggestionsAdapter( - response.reversed(), - (activity as MainActivity).searchView - ) - if (isAdded && !viewModel.searchQuery.value.isNullOrEmpty()) { - binding.suggestionsRecycler.adapter = suggestionsAdapter + if (!viewModel.searchQuery.value.isNullOrEmpty()) { + suggestionsAdapter.submitList(response.reversed()) } } } private fun showHistory() { - val searchView = runCatching { - (activity as MainActivity).searchView - }.getOrNull() - lifecycleScope.launch { val historyList = withContext(Dispatchers.IO) { Database.searchHistoryDao().getAll().map { it.query } } - if (historyList.isNotEmpty() && searchView != null) { - binding.suggestionsRecycler.adapter = SearchHistoryAdapter( - historyList, - searchView - ) + if (historyList.isNotEmpty()) { + historyAdapter.submitList(historyList) } else { binding.suggestionsRecycler.isGone = true binding.historyEmpty.isVisible = true diff --git a/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt index 47c96d1be..7f4650a10 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/SubscriptionsFragment.kt @@ -64,10 +64,9 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub private var isCurrentTabSubChannels = false private var isAppBarFullyExpanded = true - private var feedAdapter: VideosAdapter? = null + private var feedAdapter = VideosAdapter() private val sortedFeed: MutableList = mutableListOf() - private var channelsAdapter: SubscriptionChannelAdapter? = null private var selectedSortOrder = PreferenceHelper.getInt(PreferenceKeys.FEED_SORT_ORDER, 0) set(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 subFeedRecyclerViewState: Parcelable? = null + private val legacySubscriptionsAdapter = LegacySubscriptionAdapter() + private val channelsAdapter = SubscriptionChannelAdapter() + override fun setLayoutManagers(gridItems: Int) { _binding?.subFeed?.layoutManager = VideosAdapter.getLayout(requireContext(), gridItems) } @@ -94,6 +96,27 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub 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 binding.subscriptionsAppBar.addOnOffsetChangedListener { _, verticalOffset -> isAppBarFullyExpanded = verticalOffset == 0 @@ -145,7 +168,7 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub if (viewModel.subscriptions.value != null && isCurrentTabSubChannels) { binding.subRefresh.isRefreshing = true - channelsAdapter?.updateItems() + channelsAdapter.updateItems() binding.subRefresh.isRefreshing = false } } @@ -204,8 +227,6 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub private fun loadNextFeedItems() { val binding = _binding ?: return - val feedAdapter = feedAdapter ?: return - val hasMore = sortedFeed.size > feedAdapter.itemCount if (viewModel.videoFeed.value != null && !isCurrentTabSubChannels && !binding.subRefresh.isRefreshing && hasMore) { binding.subRefresh.isRefreshing = true @@ -373,11 +394,8 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub val notLoaded = viewModel.videoFeed.value.isNullOrEmpty() binding.subFeed.isGone = notLoaded binding.emptyFeed.isVisible = notLoaded - - feedAdapter = VideosAdapter(mutableListOf()) loadNextFeedItems() - binding.subFeed.adapter = feedAdapter binding.toggleSubs.text = getString(R.string.subscriptions) PreferenceHelper.updateLastFeedWatchedTime() @@ -393,18 +411,9 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub ) if (legacySubscriptions) { - binding.subChannels.layoutManager = GridLayoutManager( - context, - PreferenceHelper.getString( - PreferenceKeys.LEGACY_SUBSCRIPTIONS_COLUMNS, - "4" - ).toInt() - ) - binding.subChannels.adapter = LegacySubscriptionAdapter(subscriptions) + legacySubscriptionsAdapter.submitList(subscriptions) } else { - binding.subChannels.layoutManager = LinearLayoutManager(context) - channelsAdapter = SubscriptionChannelAdapter(subscriptions.toMutableList()) - binding.subChannels.adapter = channelsAdapter + channelsAdapter.submitList(subscriptions) } binding.subRefresh.isRefreshing = false @@ -420,7 +429,7 @@ class SubscriptionsFragment : DynamicLayoutManagerFragment(R.layout.fragment_sub } fun removeItem(videoId: String) { - feedAdapter?.removeItemById(videoId) + feedAdapter.removeItemById(videoId) sortedFeed.removeAll { it.url?.toID() != videoId } } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/TrendsFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/TrendsFragment.kt index 7cb30ed9c..441c02b19 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/TrendsFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/TrendsFragment.kt @@ -30,15 +30,18 @@ class TrendsFragment : DynamicLayoutManagerFragment(R.layout.fragment_trends) { _binding = FragmentTrendsBinding.bind(view) super.onViewCreated(view, savedInstanceState) + val adapter = VideosAdapter() + binding.recview.adapter = adapter + binding.recview.layoutManager?.onRestoreInstanceState(viewModel.recyclerViewState) + viewModel.trendingVideos.observe(viewLifecycleOwner) { videos -> if (videos == null) return@observe - binding.recview.adapter = VideosAdapter(videos.toMutableList()) - binding.recview.layoutManager?.onRestoreInstanceState(viewModel.recyclerViewState) - binding.homeRefresh.isRefreshing = false binding.progressBar.isGone = true + adapter.submitList(videos) + if (videos.isEmpty()) { Snackbar.make(binding.root, R.string.change_region, Snackbar.LENGTH_LONG) .setAction(R.string.settings) { diff --git a/app/src/main/java/com/github/libretube/ui/fragments/WatchHistoryFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/WatchHistoryFragment.kt index 4777d6c39..465da613f 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/WatchHistoryFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/WatchHistoryFragment.kt @@ -50,6 +50,8 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc private var isLoading = false private var recyclerViewState: Parcelable? = null + private val watchHistoryAdapter = WatchHistoryAdapter() + private var selectedStatusFilter = PreferenceHelper.getInt( PreferenceKeys.SELECTED_HISTORY_STATUS_FILTER, 0 @@ -80,6 +82,31 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc _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 { val history = withContext(Dispatchers.IO) { DatabaseHelper.getWatchHistoryPage(1, HISTORY_PAGE_SIZE) @@ -139,14 +166,6 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc }.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) } @@ -157,7 +176,6 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc private fun showWatchHistory(history: List) { val watchHistory = history.filterByStatusAndWatchPosition() - val watchHistoryAdapter = WatchHistoryAdapter(watchHistory.toMutableList()) binding.playAll.setOnClickListener { PlayingQueue.resetToDefaults() @@ -170,26 +188,10 @@ class WatchHistoryFragment : DynamicLayoutManagerFragment(R.layout.fragment_watc keepQueue = true ) } - - binding.watchHistoryRecView.adapter = watchHistoryAdapter + watchHistoryAdapter.submitList(history) binding.historyEmpty.isGone = 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 handler.postDelayed(200) { if (_binding == null) return@postDelayed diff --git a/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt b/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt index c7c09ae4c..e943bba8a 100644 --- a/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt +++ b/app/src/main/java/com/github/libretube/ui/preferences/InstanceSettings.kt @@ -187,8 +187,10 @@ class InstanceSettings : BasePreferenceFragment() { binding.optionsRecycler.layoutManager = LinearLayoutManager(context) val instances = ImmutableList.copyOf(this.instances) - binding.optionsRecycler.adapter = InstancesAdapter(instances, selectedIndex) { + binding.optionsRecycler.adapter = InstancesAdapter(selectedIndex) { selectedInstance = instances[it].apiUrl + }.also { + it.submitList(instances) } MaterialAlertDialogBuilder(requireContext()) diff --git a/app/src/main/java/com/github/libretube/ui/sheets/AddChannelToGroupSheet.kt b/app/src/main/java/com/github/libretube/ui/sheets/AddChannelToGroupSheet.kt index 02227bde0..48207b31a 100644 --- a/app/src/main/java/com/github/libretube/ui/sheets/AddChannelToGroupSheet.kt +++ b/app/src/main/java/com/github/libretube/ui/sheets/AddChannelToGroupSheet.kt @@ -3,7 +3,6 @@ package com.github.libretube.ui.sheets import android.os.Bundle import android.view.View import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager import com.github.libretube.R import com.github.libretube.constants.IntentData import com.github.libretube.databinding.DialogAddChannelToGroupBinding @@ -16,6 +15,10 @@ import kotlinx.coroutines.withContext class AddChannelToGroupSheet : ExpandedBottomSheet(R.layout.dialog_add_channel_to_group) { private lateinit var channelId: String + private val addToGroupAdapter by lazy(LazyThreadSafetyMode.NONE) { + AddChannelToGroupAdapter(channelId) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -26,7 +29,8 @@ class AddChannelToGroupSheet : ExpandedBottomSheet(R.layout.dialog_add_channel_t super.onViewCreated(view, savedInstanceState) val binding = DialogAddChannelToGroupBinding.bind(view) - binding.groupsRV.layoutManager = LinearLayoutManager(context) + binding.groupsRV.adapter = addToGroupAdapter + binding.cancel.setOnClickListener { requireDialog().dismiss() } @@ -36,7 +40,7 @@ class AddChannelToGroupSheet : ExpandedBottomSheet(R.layout.dialog_add_channel_t val subscriptionGroups = subGroupsDao.getAll().sortedBy { it.index }.toMutableList() withContext(Dispatchers.Main) { - binding.groupsRV.adapter = AddChannelToGroupAdapter(subscriptionGroups, channelId) + addToGroupAdapter.submitList(subscriptionGroups) binding.okay.setOnClickListener { requireDialog().hide() diff --git a/app/src/main/java/com/github/libretube/ui/sheets/EditChannelGroupSheet.kt b/app/src/main/java/com/github/libretube/ui/sheets/EditChannelGroupSheet.kt index 0b01525fa..d7b3900b9 100644 --- a/app/src/main/java/com/github/libretube/ui/sheets/EditChannelGroupSheet.kt +++ b/app/src/main/java/com/github/libretube/ui/sheets/EditChannelGroupSheet.kt @@ -30,8 +30,16 @@ class EditChannelGroupSheet : ExpandedBottomSheet(R.layout.dialog_edit_channel_g private val channelGroupsModel: EditChannelGroupsModel by activityViewModels() private var channels = listOf() + private val channelsAdapter = SubscriptionGroupChannelsAdapter( + channelGroupsModel.groupToEdit!! + ) { + channelGroupsModel.groupToEdit = it + updateConfirmStatus() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { _binding = DialogEditChannelGroupBinding.bind(view) + binding.channelsRV.adapter = channelsAdapter binding.groupName.setText(channelGroupsModel.groupToEdit?.name) val oldGroupName = channelGroupsModel.groupToEdit?.name.orEmpty() @@ -97,15 +105,12 @@ class EditChannelGroupSheet : ExpandedBottomSheet(R.layout.dialog_edit_channel_g } private fun showChannels(channels: List, 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.progress.isVisible = false + + channelsAdapter.submitList( + channels.filter { query == null || it.name.lowercase().contains(query.lowercase()) } + ) } private fun updateConfirmStatus() { diff --git a/app/src/main/java/com/github/libretube/ui/views/DescriptionLayout.kt b/app/src/main/java/com/github/libretube/ui/views/DescriptionLayout.kt index 733217a61..aa9f9a8c2 100644 --- a/app/src/main/java/com/github/libretube/ui/views/DescriptionLayout.kt +++ b/app/src/main/java/com/github/libretube/ui/views/DescriptionLayout.kt @@ -10,7 +10,6 @@ import androidx.core.text.method.LinkMovementMethodCompat import androidx.core.text.parseAsHtml import androidx.core.view.isGone import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager import com.github.libretube.R import com.github.libretube.api.obj.Segment import com.github.libretube.api.obj.Streams @@ -33,6 +32,8 @@ class DescriptionLayout( private var streams: Streams? = null var handleLink: (link: String) -> Unit = {} + private val videoTagsAdapter = VideoTagsAdapter() + init { binding.playerTitleLayout.setOnClickListener { toggleDescription() @@ -41,6 +42,8 @@ class DescriptionLayout( streams?.title?.let { ClipboardHelper.save(context, text = it) } true } + + binding.tagsRecycler.adapter = videoTagsAdapter } fun setSegments(segments: List) { @@ -101,9 +104,7 @@ class DescriptionLayout( "${context?.getString(R.string.visibility)}: $visibility" if (streams.tags.isNotEmpty()) { - binding.tagsRecycler.layoutManager = - LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) - binding.tagsRecycler.adapter = VideoTagsAdapter(streams.tags) + videoTagsAdapter.submitList(streams.tags) } binding.tagsRecycler.isVisible = streams.tags.isNotEmpty() diff --git a/app/src/main/res/layout/description_layout.xml b/app/src/main/res/layout/description_layout.xml index 0d0985dd3..03b6b76f0 100644 --- a/app/src/main/res/layout/description_layout.xml +++ b/app/src/main/res/layout/description_layout.xml @@ -20,8 +20,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:checkable="false" - android:visibility="gone" - android:text="Sponsor"/> + android:text="Sponsor" + android:visibility="gone" /> @@ -133,7 +133,9 @@ + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> diff --git a/app/src/main/res/layout/dialog_add_channel_to_group.xml b/app/src/main/res/layout/dialog_add_channel_to_group.xml index 04f3bd827..9ab588680 100644 --- a/app/src/main/res/layout/dialog_add_channel_to_group.xml +++ b/app/src/main/res/layout/dialog_add_channel_to_group.xml @@ -1,6 +1,7 @@ @@ -48,7 +49,9 @@ android:id="@+id/groupsRV" android:layout_width="match_parent" 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" /> @@ -35,10 +36,12 @@ android:id="@+id/featuredRV" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingHorizontal="10dp" android:clipToPadding="false" android:nestedScrollingEnabled="false" - android:visibility="gone" /> + android:orientation="horizontal" + android:paddingHorizontal="10dp" + android:visibility="gone" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> + android:orientation="horizontal" + android:paddingHorizontal="10dp" + android:visibility="gone" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> + android:visibility="gone" + app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" + app:spanCount="2" /> @@ -84,7 +91,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:nestedScrollingEnabled="false" - android:visibility="gone" /> + android:orientation="horizontal" + android:visibility="gone" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> + android:visibility="gone" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> @@ -134,8 +144,8 @@ android:layout_height="50dp" android:layout_gravity="center" android:layout_marginTop="30dp" - android:textSize="12sp" - android:text="@string/retry"/> + android:text="@string/retry" + android:textSize="12sp" /> + android:text="@string/change_instance" + android:textSize="12sp" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_suggestions.xml b/app/src/main/res/layout/fragment_search_suggestions.xml index 2110aa7f0..61536cb6a 100644 --- a/app/src/main/res/layout/fragment_search_suggestions.xml +++ b/app/src/main/res/layout/fragment_search_suggestions.xml @@ -1,5 +1,6 @@ + android:layout_marginVertical="10dp" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:reverseLayout="true" + app:stackFromEnd="true" />