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:
parent
5f6cdad507
commit
87ca4c8ab1
13 changed files with 227 additions and 93 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue