search: add playing indicators [#218]
Add playing indicators to the search view too.
This commit is contained in:
parent
87ca4c8ab1
commit
227a258eca
8 changed files with 100 additions and 16 deletions
|
|
@ -87,7 +87,7 @@ class AlbumDetailFragment :
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
setSpanSizeLookup { pos ->
|
setSpanSizeLookup { pos ->
|
||||||
val item = detailModel.albumData.value[pos]
|
val item = detailAdapter.currentList[pos]
|
||||||
item is Album || item is Header || item is SortHeader
|
item is Album || item is Header || item is SortHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ class ArtistDetailFragment :
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
setSpanSizeLookup { pos ->
|
setSpanSizeLookup { pos ->
|
||||||
val item = detailModel.artistData.value[pos]
|
val item = detailAdapter.currentList[pos]
|
||||||
item is Artist || item is Header || item is SortHeader
|
item is Artist || item is Header || item is SortHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class GenreDetailFragment :
|
||||||
binding.detailRecycler.apply {
|
binding.detailRecycler.apply {
|
||||||
adapter = detailAdapter
|
adapter = detailAdapter
|
||||||
setSpanSizeLookup { pos ->
|
setSpanSizeLookup { pos ->
|
||||||
val item = detailModel.genreData.value[pos]
|
val item = detailAdapter.currentList[pos]
|
||||||
item is Genre || item is Header || item is SortHeader
|
item is Genre || item is Header || item is SortHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,9 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
|
||||||
|
|
||||||
protected val differ = AsyncListDiffer(this, diffCallback)
|
protected val differ = AsyncListDiffer(this, diffCallback)
|
||||||
|
|
||||||
|
val currentList: List<Item>
|
||||||
|
get() = differ.currentList
|
||||||
|
|
||||||
fun submitList(list: List<Item>) {
|
fun submitList(list: List<Item>) {
|
||||||
differ.submitList(list)
|
differ.submitList(list)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
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.ArtistViewHolder
|
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
|
||||||
import org.oxycblt.auxio.ui.recycler.GenreViewHolder
|
import org.oxycblt.auxio.ui.recycler.GenreViewHolder
|
||||||
|
|
@ -35,8 +36,12 @@ import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
|
||||||
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
import org.oxycblt.auxio.ui.recycler.SongViewHolder
|
||||||
|
|
||||||
class SearchAdapter(private val listener: MenuItemListener) :
|
class SearchAdapter(private val listener: MenuItemListener) :
|
||||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
ActivationAdapter<RecyclerView.ViewHolder>() {
|
||||||
private val differ = AsyncListDiffer(this, DIFFER)
|
private val differ = AsyncListDiffer(this, DIFFER)
|
||||||
|
private var currentSong: Song? = null
|
||||||
|
private var currentAlbum: Album? = null
|
||||||
|
private var currentArtist: Artist? = null
|
||||||
|
private var currentGenre: Genre? = null
|
||||||
|
|
||||||
override fun getItemCount() = differ.currentList.size
|
override fun getItemCount() = differ.currentList.size
|
||||||
|
|
||||||
|
|
@ -45,7 +50,7 @@ class SearchAdapter(private val listener: MenuItemListener) :
|
||||||
is Song -> SongViewHolder.VIEW_TYPE
|
is Song -> SongViewHolder.VIEW_TYPE
|
||||||
is Album -> AlbumViewHolder.VIEW_TYPE
|
is Album -> AlbumViewHolder.VIEW_TYPE
|
||||||
is Artist -> ArtistViewHolder.VIEW_TYPE
|
is Artist -> ArtistViewHolder.VIEW_TYPE
|
||||||
is Genre -> HeaderViewHolder.VIEW_TYPE
|
is Genre -> GenreViewHolder.VIEW_TYPE
|
||||||
is Header -> HeaderViewHolder.VIEW_TYPE
|
is Header -> HeaderViewHolder.VIEW_TYPE
|
||||||
else -> super.getItemViewType(position)
|
else -> super.getItemViewType(position)
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +65,14 @@ class SearchAdapter(private val listener: MenuItemListener) :
|
||||||
else -> error("Invalid item type $viewType")
|
else -> error("Invalid item type $viewType")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(
|
||||||
|
holder: RecyclerView.ViewHolder,
|
||||||
|
position: Int,
|
||||||
|
payloads: List<Any>
|
||||||
|
) {
|
||||||
|
super.onBindViewHolder(holder, position, payloads)
|
||||||
|
|
||||||
|
if (payloads.isEmpty()) {
|
||||||
when (val item = differ.currentList[position]) {
|
when (val item = differ.currentList[position]) {
|
||||||
is Song -> (holder as SongViewHolder).bind(item, listener)
|
is Song -> (holder as SongViewHolder).bind(item, listener)
|
||||||
is Album -> (holder as AlbumViewHolder).bind(item, listener)
|
is Album -> (holder as AlbumViewHolder).bind(item, listener)
|
||||||
|
|
@ -69,9 +81,46 @@ class SearchAdapter(private val listener: MenuItemListener) :
|
||||||
is Header -> (holder as HeaderViewHolder).bind(item)
|
is Header -> (holder as HeaderViewHolder).bind(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldActivateViewHolder(position: Int): Boolean {
|
||||||
|
val item = differ.currentList[position]
|
||||||
|
|
||||||
|
return (item is Song && item.id == currentSong?.id) ||
|
||||||
|
(item is Album && item.id == currentAlbum?.id) ||
|
||||||
|
(item is Artist && item.id == currentArtist?.id) ||
|
||||||
|
(item is Genre && item.id == currentGenre?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentList: List<Item>
|
||||||
|
get() = differ.currentList
|
||||||
|
|
||||||
fun submitList(list: List<Item>, callback: () -> Unit) = differ.submitList(list, callback)
|
fun submitList(list: List<Item>, callback: () -> Unit) = differ.submitList(list, callback)
|
||||||
|
|
||||||
|
fun activateSong(song: Song?) {
|
||||||
|
if (song == currentSong) return
|
||||||
|
activateImpl(differ.currentList, currentSong, song)
|
||||||
|
currentSong = song
|
||||||
|
}
|
||||||
|
|
||||||
|
fun activateAlbum(album: Album?) {
|
||||||
|
if (album == currentAlbum) return
|
||||||
|
activateImpl(differ.currentList, currentAlbum, album)
|
||||||
|
currentAlbum = album
|
||||||
|
}
|
||||||
|
|
||||||
|
fun activateArtist(artist: Artist?) {
|
||||||
|
if (artist == currentArtist) return
|
||||||
|
activateImpl(differ.currentList, currentArtist, artist)
|
||||||
|
currentArtist = artist
|
||||||
|
}
|
||||||
|
|
||||||
|
fun activateGenre(genre: Genre?) {
|
||||||
|
if (genre == currentGenre) return
|
||||||
|
activateImpl(differ.currentList, currentGenre, genre)
|
||||||
|
currentGenre = genre
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DIFFER =
|
private val DIFFER =
|
||||||
object : SimpleItemCallback<Item>() {
|
object : SimpleItemCallback<Item>() {
|
||||||
|
|
|
||||||
|
|
@ -106,12 +106,13 @@ class SearchFragment :
|
||||||
|
|
||||||
binding.searchRecycler.apply {
|
binding.searchRecycler.apply {
|
||||||
adapter = searchAdapter
|
adapter = searchAdapter
|
||||||
setSpanSizeLookup { pos -> searchModel.searchResults.value[pos] is Header }
|
setSpanSizeLookup { pos -> searchAdapter.currentList[pos] is Header }
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
collectImmediately(searchModel.searchResults, ::updateResults)
|
collectImmediately(searchModel.searchResults, ::handleResults)
|
||||||
|
collectImmediately(playbackModel.song, playbackModel.parent, ::handlePlayback)
|
||||||
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
collect(navModel.exploreNavigationItem, ::handleNavigation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,7 +152,7 @@ class SearchFragment :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateResults(results: List<Item>) {
|
private fun handleResults(results: List<Item>) {
|
||||||
val binding = requireBinding()
|
val binding = requireBinding()
|
||||||
|
|
||||||
searchAdapter.submitList(results.toMutableList()) {
|
searchAdapter.submitList(results.toMutableList()) {
|
||||||
|
|
@ -164,6 +165,36 @@ class SearchFragment :
|
||||||
binding.searchRecycler.isInvisible = results.isEmpty()
|
binding.searchRecycler.isInvisible = results.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handlePlayback(song: Song?, parent: MusicParent?) {
|
||||||
|
if (parent == null) {
|
||||||
|
searchAdapter.activateSong(song)
|
||||||
|
} else {
|
||||||
|
// Ignore playback not from all songs
|
||||||
|
searchAdapter.activateSong(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent is Album) {
|
||||||
|
searchAdapter.activateAlbum(parent)
|
||||||
|
} else {
|
||||||
|
// Ignore playback not from albums
|
||||||
|
searchAdapter.activateAlbum(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent is Artist) {
|
||||||
|
searchAdapter.activateArtist(parent)
|
||||||
|
} else {
|
||||||
|
// Ignore playback not from artists
|
||||||
|
searchAdapter.activateArtist(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent is Genre) {
|
||||||
|
searchAdapter.activateGenre(parent)
|
||||||
|
} else {
|
||||||
|
// Ignore playback not from artists
|
||||||
|
searchAdapter.activateGenre(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleNavigation(item: Music?) {
|
private fun handleNavigation(item: Music?) {
|
||||||
findNavController()
|
findNavController()
|
||||||
.navigate(
|
.navigate(
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
return insets
|
return insets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move abstraction to adapters since using external data will not work well
|
||||||
inline fun setSpanSizeLookup(crossinline fullWidth: (Int) -> Boolean) {
|
inline fun setSpanSizeLookup(crossinline fullWidth: (Int) -> Boolean) {
|
||||||
val glm = layoutManager as GridLayoutManager
|
val glm = layoutManager as GridLayoutManager
|
||||||
val spanCount = glm.spanCount
|
val spanCount = glm.spanCount
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue