home: indicate playback on items [#218]

Indicate playback in the home view as well.

This is mostly a QoL change. Might also add this to the search view.
This commit is contained in:
Alexander Capehart 2022-09-01 19:27:36 -06:00
parent 5f6cdad507
commit 87ca4c8ab1
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
13 changed files with 227 additions and 93 deletions

View file

@ -257,10 +257,10 @@ class AlbumDetailFragment :
} }
if (parent is Album && parent.id == unlikelyToBeNull(detailModel.currentAlbum.value).id) { if (parent is Album && parent.id == unlikelyToBeNull(detailModel.currentAlbum.value).id) {
detailAdapter.highlightSong(song) detailAdapter.activateSong(song)
} else { } else {
// Clear the ViewHolders if the mode isn't ALL_SONGS // Clear the ViewHolders if the mode isn't ALL_SONGS
detailAdapter.highlightSong(null) detailAdapter.activateSong(null)
} }
} }

View file

@ -202,18 +202,18 @@ class ArtistDetailFragment :
private fun updatePlayback(song: Song?, parent: MusicParent?) { private fun updatePlayback(song: Song?, parent: MusicParent?) {
if (parent is Artist && parent.id == unlikelyToBeNull(detailModel.currentArtist.value).id) { if (parent is Artist && parent.id == unlikelyToBeNull(detailModel.currentArtist.value).id) {
detailAdapter.highlightSong(song) detailAdapter.activateSong(song)
} else { } else {
// Clear the ViewHolders if the given song is not part of this artist. // Ignore song playback not from the artist
detailAdapter.highlightSong(null) detailAdapter.activateSong(null)
} }
if (parent is Album && if (parent is Album &&
parent.artist.id == unlikelyToBeNull(detailModel.currentArtist.value).id) { parent.artist.id == unlikelyToBeNull(detailModel.currentArtist.value).id) {
detailAdapter.highlightAlbum(parent) detailAdapter.activateAlbum(parent)
} else { } else {
// Clear out the album viewholder if the parent is not an album. // Ignore album playback not from the artist
detailAdapter.highlightAlbum(null) detailAdapter.activateAlbum(null)
} }
} }
} }

View file

@ -83,7 +83,7 @@ class GenreDetailFragment :
binding.detailRecycler.apply { binding.detailRecycler.apply {
adapter = detailAdapter adapter = detailAdapter
setSpanSizeLookup { pos -> setSpanSizeLookup { pos ->
val item = detailModel.albumData.value[pos] val item = detailModel.genreData.value[pos]
item is Genre || item is Header || item is SortHeader item is Genre || item is Header || item is SortHeader
} }
} }
@ -195,9 +195,10 @@ class GenreDetailFragment :
private fun updatePlayback(song: Song?, parent: MusicParent?) { private fun updatePlayback(song: Song?, parent: MusicParent?) {
if (parent is Genre && parent.id == unlikelyToBeNull(detailModel.currentGenre.value).id) { if (parent is Genre && parent.id == unlikelyToBeNull(detailModel.currentGenre.value).id) {
detailAdapter.highlightSong(song) detailAdapter.activateSong(song)
} else { } else {
detailAdapter.highlightSong(null) // Ignore song playback not from the genre
detailAdapter.activateSong(null)
} }
} }
} }

View file

@ -64,25 +64,28 @@ class AlbumDetailAdapter(private val listener: Listener) :
override fun onBindViewHolder( override fun onBindViewHolder(
holder: RecyclerView.ViewHolder, holder: RecyclerView.ViewHolder,
position: Int, position: Int,
payload: List<Any> payloads: List<Any>
) { ) {
if (payload.isEmpty()) { super.onBindViewHolder(holder, position, payloads)
if (payloads.isEmpty()) {
when (val item = differ.currentList[position]) { when (val item = differ.currentList[position]) {
is Album -> (holder as AlbumDetailViewHolder).bind(item, listener) is Album -> (holder as AlbumDetailViewHolder).bind(item, listener)
is DiscHeader -> (holder as DiscHeaderViewHolder).bind(item) is DiscHeader -> (holder as DiscHeaderViewHolder).bind(item)
is Song -> (holder as AlbumSongViewHolder).bind(item, listener) is Song -> (holder as AlbumSongViewHolder).bind(item, listener)
} }
} }
super.onBindViewHolder(holder, position, payload)
} }
override fun shouldHighlightViewHolder(item: Item) = item is Song && item.id == currentSong?.id override fun shouldActivateViewHolder(position: Int): Boolean {
val item = differ.currentList[position]
return item is Song && item.id == currentSong?.id
}
/** Update the [song] that this adapter should highlight */ /** Update the [song] that this adapter should indicate playback */
fun highlightSong(song: Song?) { fun activateSong(song: Song?) {
if (song == currentSong) return if (song == currentSong) return
highlightImpl(currentSong, song) activateImpl(differ.currentList, currentSong, song)
currentSong = song currentSong = song
} }

View file

@ -66,34 +66,36 @@ class ArtistDetailAdapter(private val listener: Listener) :
override fun onBindViewHolder( override fun onBindViewHolder(
holder: RecyclerView.ViewHolder, holder: RecyclerView.ViewHolder,
position: Int, position: Int,
payload: List<Any> payloads: List<Any>
) { ) {
if (payload.isEmpty()) { super.onBindViewHolder(holder, position, payloads)
if (payloads.isEmpty()) {
when (val item = differ.currentList[position]) { when (val item = differ.currentList[position]) {
is Artist -> (holder as ArtistDetailViewHolder).bind(item, listener) is Artist -> (holder as ArtistDetailViewHolder).bind(item, listener)
is Album -> (holder as ArtistAlbumViewHolder).bind(item, listener) is Album -> (holder as ArtistAlbumViewHolder).bind(item, listener)
is Song -> (holder as ArtistSongViewHolder).bind(item, listener) is Song -> (holder as ArtistSongViewHolder).bind(item, listener)
} }
} }
super.onBindViewHolder(holder, position, payload)
} }
override fun shouldHighlightViewHolder(item: Item) = override fun shouldActivateViewHolder(position: Int): Boolean {
(item is Album && item.id == currentAlbum?.id) || val item = differ.currentList[position]
return (item is Album && item.id == currentAlbum?.id) ||
(item is Song && item.id == currentSong?.id) (item is Song && item.id == currentSong?.id)
}
/** Update the current [album] that this adapter should highlight */ /** Update the [album] that this adapter should indicate playback */
fun highlightAlbum(album: Album?) { fun activateAlbum(album: Album?) {
if (album == currentAlbum) return if (album == currentAlbum) return
highlightImpl(currentAlbum, album) activateImpl(differ.currentList, currentAlbum, album)
currentAlbum = album currentAlbum = album
} }
/** Update the [song] that this adapter should highlight */ /** Update the [song] that this adapter should indicate playback */
fun highlightSong(song: Song?) { fun activateSong(song: Song?) {
if (song == currentSong) return if (song == currentSong) return
highlightImpl(currentSong, song) activateImpl(differ.currentList, currentSong, song)
currentSong = song currentSong = song
} }

View file

@ -26,6 +26,7 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.databinding.ItemSortHeaderBinding import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
import org.oxycblt.auxio.detail.SortHeader import org.oxycblt.auxio.detail.SortHeader
import org.oxycblt.auxio.ui.recycler.ActivationAdapter
import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.HeaderViewHolder import org.oxycblt.auxio.ui.recycler.HeaderViewHolder
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
@ -33,12 +34,11 @@ import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.logW
abstract class DetailAdapter<L : DetailAdapter.Listener>( abstract class DetailAdapter<L : DetailAdapter.Listener>(
private val listener: L, private val listener: L,
diffCallback: DiffUtil.ItemCallback<Item> diffCallback: DiffUtil.ItemCallback<Item>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : ActivationAdapter<RecyclerView.ViewHolder>() {
@Suppress("LeakingThis") override fun getItemCount() = differ.currentList.size @Suppress("LeakingThis") override fun getItemCount() = differ.currentList.size
override fun getItemViewType(position: Int) = override fun getItemViewType(position: Int) =
@ -61,58 +61,27 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
override fun onBindViewHolder( override fun onBindViewHolder(
holder: RecyclerView.ViewHolder, holder: RecyclerView.ViewHolder,
position: Int, position: Int,
payload: List<Any> payloads: List<Any>
) { ) {
val item = differ.currentList[position] val item = differ.currentList[position]
if (payload.isEmpty()) { if (payloads.isEmpty()) {
when (item) { when (item) {
is Header -> (holder as HeaderViewHolder).bind(item) is Header -> (holder as HeaderViewHolder).bind(item)
is SortHeader -> (holder as SortHeaderViewHolder).bind(item, listener) is SortHeader -> (holder as SortHeaderViewHolder).bind(item, listener)
} }
} }
holder.itemView.isActivated = shouldHighlightViewHolder(item) super.onBindViewHolder(holder, position, payloads)
} }
protected val differ = AsyncListDiffer(this, diffCallback) protected val differ = AsyncListDiffer(this, diffCallback)
protected abstract fun shouldHighlightViewHolder(item: Item): Boolean
protected inline fun <reified T : Item> highlightImpl(oldItem: T?, newItem: T?) {
if (oldItem != null) {
val pos = differ.currentList.indexOfFirst { item -> item.id == oldItem.id && item is T }
if (pos > -1) {
notifyItemChanged(pos, PAYLOAD_HIGHLIGHT_CHANGED)
} else {
logW("oldItem was not in adapter data")
}
}
if (newItem != null) {
val pos = differ.currentList.indexOfFirst { item -> item is T && item.id == newItem.id }
if (pos > -1) {
notifyItemChanged(pos, PAYLOAD_HIGHLIGHT_CHANGED)
} else {
logW("newItem was not in adapter data")
}
}
}
fun submitList(list: List<Item>) { fun submitList(list: List<Item>) {
differ.submitList(list) differ.submitList(list)
} }
companion object { companion object {
// This payload value serves two purposes:
// 1. It disables animations from notifyItemChanged, which looks bad when highlighting
// ViewHolders.
// 2. It instructs adapters to avoid re-binding information, and instead simply
// change the highlight state.
val PAYLOAD_HIGHLIGHT_CHANGED = Any()
val DIFFER = val DIFFER =
object : SimpleItemCallback<Item>() { object : SimpleItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean { override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {

View file

@ -58,24 +58,27 @@ class GenreDetailAdapter(private val listener: Listener) :
override fun onBindViewHolder( override fun onBindViewHolder(
holder: RecyclerView.ViewHolder, holder: RecyclerView.ViewHolder,
position: Int, position: Int,
payload: List<Any> payloads: List<Any>
) { ) {
if (payload.isEmpty()) { super.onBindViewHolder(holder, position, payloads)
if (payloads.isEmpty()) {
when (val item = differ.currentList[position]) { when (val item = differ.currentList[position]) {
is Genre -> (holder as GenreDetailViewHolder).bind(item, listener) is Genre -> (holder as GenreDetailViewHolder).bind(item, listener)
is Song -> (holder as SongViewHolder).bind(item, listener) is Song -> (holder as SongViewHolder).bind(item, listener)
} }
} }
super.onBindViewHolder(holder, position, payload)
} }
override fun shouldHighlightViewHolder(item: Item) = item is Song && item.id == currentSong?.id override fun shouldActivateViewHolder(position: Int): Boolean {
val item = differ.currentList[position]
return item is Song && item.id == currentSong?.id
}
/** Update the [song] that this adapter should highlight */ /** Update the [song] that this adapter should indicate playback */
fun highlightSong(song: Song?) { fun activateSong(song: Song?) {
if (song == currentSong) return if (song == currentSong) return
highlightImpl(currentSong, song) activateImpl(differ.currentList, currentSong, song)
currentSong = song currentSong = song
} }

View file

@ -21,14 +21,15 @@ import android.os.Bundle
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import java.util.* import java.util.*
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.ActivationAdapter
import org.oxycblt.auxio.ui.recycler.AlbumViewHolder import org.oxycblt.auxio.ui.recycler.AlbumViewHolder
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
@ -55,6 +56,7 @@ class AlbumListFragment : HomeListFragment<Album>() {
} }
collectImmediately(homeModel.albums, homeAdapter::replaceList) collectImmediately(homeModel.albums, homeAdapter::replaceList)
collectImmediately(playbackModel.parent, ::handleParent)
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {
@ -107,21 +109,47 @@ class AlbumListFragment : HomeListFragment<Album>() {
} }
} }
private fun handleParent(parent: MusicParent?) {
if (parent is Album) {
homeAdapter.activateAlbum(parent)
} else {
// Ignore playback not from albums
homeAdapter.activateAlbum(null)
}
}
private class AlbumAdapter(private val listener: MenuItemListener) : private class AlbumAdapter(private val listener: MenuItemListener) :
RecyclerView.Adapter<AlbumViewHolder>() { ActivationAdapter<AlbumViewHolder>() {
private val differ = SyncListDiffer(this, AlbumViewHolder.DIFFER) private val differ = SyncListDiffer(this, AlbumViewHolder.DIFFER)
private var currentAlbum: Album? = null
override fun getItemCount() = differ.currentList.size override fun getItemCount() = differ.currentList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
AlbumViewHolder.new(parent) AlbumViewHolder.new(parent)
override fun onBindViewHolder(holder: AlbumViewHolder, position: Int) { override fun onBindViewHolder(holder: AlbumViewHolder, position: Int, payloads: List<Any>) {
holder.bind(differ.currentList[position], listener) super.onBindViewHolder(holder, position, payloads)
if (payloads.isEmpty()) {
holder.bind(differ.currentList[position], listener)
}
}
override fun shouldActivateViewHolder(position: Int): Boolean {
val item = differ.currentList[position]
return item.id == currentAlbum?.id
} }
fun replaceList(newList: List<Album>) { fun replaceList(newList: List<Album>) {
differ.replaceList(newList) differ.replaceList(newList)
} }
/** Update the [album] that this adapter should indicate playback */
fun activateAlbum(album: Album?) {
if (album == currentAlbum) return
activateImpl(differ.currentList, currentAlbum, album)
currentAlbum = album
}
} }
} }

View file

@ -20,13 +20,14 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.ActivationAdapter
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
@ -50,6 +51,7 @@ class ArtistListFragment : HomeListFragment<Artist>() {
} }
collectImmediately(homeModel.artists, homeAdapter::replaceList) collectImmediately(homeModel.artists, homeAdapter::replaceList)
collectImmediately(playbackModel.parent, ::handleParent)
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {
@ -83,21 +85,51 @@ class ArtistListFragment : HomeListFragment<Artist>() {
} }
} }
private fun handleParent(parent: MusicParent?) {
if (parent is Artist) {
homeAdapter.activateArtist(parent)
} else {
// Ignore playback not from artists
homeAdapter.activateArtist(null)
}
}
private class ArtistAdapter(private val listener: MenuItemListener) : private class ArtistAdapter(private val listener: MenuItemListener) :
RecyclerView.Adapter<ArtistViewHolder>() { ActivationAdapter<ArtistViewHolder>() {
private val differ = SyncListDiffer(this, ArtistViewHolder.DIFFER) private val differ = SyncListDiffer(this, ArtistViewHolder.DIFFER)
private var currentArtist: Artist? = null
override fun getItemCount() = differ.currentList.size override fun getItemCount() = differ.currentList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
ArtistViewHolder.new(parent) ArtistViewHolder.new(parent)
override fun onBindViewHolder(holder: ArtistViewHolder, position: Int) { override fun onBindViewHolder(
holder.bind(differ.currentList[position], listener) holder: ArtistViewHolder,
position: Int,
payloads: List<Any>
) {
super.onBindViewHolder(holder, position, payloads)
if (payloads.isEmpty()) {
holder.bind(differ.currentList[position], listener)
}
}
override fun shouldActivateViewHolder(position: Int): Boolean {
val item = differ.currentList[position]
return item.id == currentArtist?.id
} }
fun replaceList(newList: List<Artist>) { fun replaceList(newList: List<Artist>) {
differ.replaceList(newList) differ.replaceList(newList)
} }
/** Update the [artist] that this adapter should indicate playback */
fun activateArtist(artist: Artist?) {
if (artist == currentArtist) return
activateImpl(differ.currentList, currentArtist, artist)
currentArtist = artist
}
} }
} }

View file

@ -20,13 +20,14 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.ActivationAdapter
import org.oxycblt.auxio.ui.recycler.GenreViewHolder import org.oxycblt.auxio.ui.recycler.GenreViewHolder
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
@ -50,6 +51,7 @@ class GenreListFragment : HomeListFragment<Genre>() {
} }
collectImmediately(homeModel.genres, homeAdapter::replaceList) collectImmediately(homeModel.genres, homeAdapter::replaceList)
collectImmediately(playbackModel.parent, ::handlePlayback)
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {
@ -83,21 +85,47 @@ class GenreListFragment : HomeListFragment<Genre>() {
} }
} }
private fun handlePlayback(parent: MusicParent?) {
if (parent is Genre) {
homeAdapter.activateGenre(parent)
} else {
// Ignore playback not from genres
homeAdapter.activateGenre(null)
}
}
private class GenreAdapter(private val listener: MenuItemListener) : private class GenreAdapter(private val listener: MenuItemListener) :
RecyclerView.Adapter<GenreViewHolder>() { ActivationAdapter<GenreViewHolder>() {
private val differ = SyncListDiffer(this, GenreViewHolder.DIFFER) private val differ = SyncListDiffer(this, GenreViewHolder.DIFFER)
private var currentGenre: Genre? = null
override fun getItemCount() = differ.currentList.size override fun getItemCount() = differ.currentList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
GenreViewHolder.new(parent) GenreViewHolder.new(parent)
override fun onBindViewHolder(holder: GenreViewHolder, position: Int) { override fun onBindViewHolder(holder: GenreViewHolder, position: Int, payloads: List<Any>) {
holder.bind(differ.currentList[position], listener) super.onBindViewHolder(holder, position, payloads)
if (payloads.isEmpty()) {
holder.bind(differ.currentList[position], listener)
}
}
override fun shouldActivateViewHolder(position: Int): Boolean {
val item = differ.currentList[position]
return item.id == currentGenre?.id
} }
fun replaceList(newList: List<Genre>) { fun replaceList(newList: List<Genre>) {
differ.replaceList(newList) differ.replaceList(newList)
} }
/** Update the [genre] that this adapter should indicate playback */
fun activateGenre(genre: Genre?) {
if (genre == currentGenre) return
activateImpl(differ.currentList, currentGenre, genre)
currentGenre = genre
}
} }
} }

View file

@ -21,14 +21,15 @@ import android.os.Bundle
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import java.util.Formatter import java.util.Formatter
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.ActivationAdapter
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.SongViewHolder import org.oxycblt.auxio.ui.recycler.SongViewHolder
@ -57,6 +58,7 @@ class SongListFragment : HomeListFragment<Song>() {
} }
collectImmediately(homeModel.songs, homeAdapter::replaceList) collectImmediately(homeModel.songs, homeAdapter::replaceList)
collectImmediately(playbackModel.song, playbackModel.parent, ::handlePlayback)
} }
override fun getPopup(pos: Int): String? { override fun getPopup(pos: Int): String? {
@ -111,21 +113,47 @@ class SongListFragment : HomeListFragment<Song>() {
} }
} }
private fun handlePlayback(song: Song?, parent: MusicParent?) {
if (parent == null) {
homeAdapter.activateSong(song)
} else {
// Ignore playback that is not from all songs
homeAdapter.activateSong(null)
}
}
private class SongAdapter(private val listener: MenuItemListener) : private class SongAdapter(private val listener: MenuItemListener) :
RecyclerView.Adapter<SongViewHolder>() { ActivationAdapter<SongViewHolder>() {
private val differ = SyncListDiffer(this, SongViewHolder.DIFFER) private val differ = SyncListDiffer(this, SongViewHolder.DIFFER)
private var currentSong: Song? = null
override fun getItemCount() = differ.currentList.size override fun getItemCount() = differ.currentList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
SongViewHolder.new(parent) SongViewHolder.new(parent)
override fun onBindViewHolder(holder: SongViewHolder, position: Int) { override fun onBindViewHolder(holder: SongViewHolder, position: Int, payloads: List<Any>) {
holder.bind(differ.currentList[position], listener) super.onBindViewHolder(holder, position, payloads)
if (payloads.isEmpty()) {
holder.bind(differ.currentList[position], listener)
}
}
override fun shouldActivateViewHolder(position: Int): Boolean {
val item = differ.currentList[position]
return item.id == currentSong?.id
} }
fun replaceList(newList: List<Song>) { fun replaceList(newList: List<Song>) {
differ.replaceList(newList) differ.replaceList(newList)
} }
/** Update the [song] that this adapter should indicate playback */
fun activateSong(song: Song?) {
if (song == currentSong) return
activateImpl(differ.currentList, currentSong, song)
currentSong = song
}
} }
} }

View file

@ -23,6 +23,7 @@ import androidx.recyclerview.widget.AdapterListUpdateCallback
import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.util.logW
/** /**
* The base for all items in Auxio. Any datatype can derive this type and gain some behavior not * The base for all items in Auxio. Any datatype can derive this type and gain some behavior not
@ -171,3 +172,43 @@ abstract class SimpleItemCallback<T : Item> : DiffUtil.ItemCallback<T>() {
return oldItem.id == newItem.id return oldItem.id == newItem.id
} }
} }
abstract class ActivationAdapter<VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
override fun onBindViewHolder(holder: VH, position: Int) = throw UnsupportedOperationException()
override fun onBindViewHolder(holder: VH, position: Int, payloads: List<Any>) {
holder.itemView.isActivated = shouldActivateViewHolder(position)
}
protected abstract fun shouldActivateViewHolder(position: Int): Boolean
protected inline fun <reified T : Item> activateImpl(
currentList: List<Item>,
oldItem: T?,
newItem: T?
) {
if (oldItem != null) {
val pos = currentList.indexOfFirst { item -> item.id == oldItem.id && item is T }
if (pos > -1) {
notifyItemChanged(pos, PAYLOAD_ACTIVATION_CHANGED)
} else {
logW("oldItem was not in adapter data")
}
}
if (newItem != null) {
val pos = currentList.indexOfFirst { item -> item is T && item.id == newItem.id }
if (pos > -1) {
notifyItemChanged(pos, PAYLOAD_ACTIVATION_CHANGED)
} else {
logW("newItem was not in adapter data")
}
}
}
companion object {
val PAYLOAD_ACTIVATION_CHANGED = Any()
}
}

View file

@ -213,8 +213,7 @@ list similar to this:
`Item being displayed | Header Item | Child Item | Child Item | Child Item...` `Item being displayed | Header Item | Child Item | Child Item | Child Item...`
Note that the actual dataset used is more complex once sorting and disc numbers is taken into Note that the actual dataset used is more complex once sorting and disc numbers is taken into
account. Item highlighting and certain shared ViewHolders are already managed by the `DetailAdapter` account.
super-class, which should be implemented by all adapters in the detail UI.
#### `.home` #### `.home`
This package contains the components for the "home" UI in Auxio, or the UI that the user first sees This package contains the components for the "home" UI in Auxio, or the UI that the user first sees