Add artist songs list

Add a list of artist songs to ArtistDetailFragment. This moves the artist sort functionality to the song list instead of the album list.
This commit is contained in:
OxygenCobalt 2021-04-26 16:54:49 -06:00
parent cc72ebc251
commit b9506bcbc3
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
17 changed files with 254 additions and 110 deletions

View file

@ -65,7 +65,9 @@ class AlbumDetailFragment : DetailFragment() {
} }
} }
setupRecycler(detailAdapter) setupRecycler(detailAdapter) { pos ->
pos == 0
}
// -- DETAILVIEWMODEL SETUP --- // -- DETAILVIEWMODEL SETUP ---

View file

@ -6,19 +6,22 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.adapters.ArtistDetailAdapter import org.oxycblt.auxio.detail.adapters.ArtistDetailAdapter
import org.oxycblt.auxio.logD import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.ui.newMenu
/** /**
* The [DetailFragment] for an artist. * The [DetailFragment] for an artist.
* TODO: Show a list of songs?
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class ArtistDetailFragment : DetailFragment() { class ArtistDetailFragment : DetailFragment() {
@ -42,14 +45,18 @@ class ArtistDetailFragment : DetailFragment() {
} }
val detailAdapter = ArtistDetailAdapter( val detailAdapter = ArtistDetailAdapter(
detailModel, playbackModel, viewLifecycleOwner, playbackModel,
doOnClick = { album -> doOnClick = { data ->
if (!detailModel.isNavigating) { if (data is Album) {
detailModel.setNavigating(true) if (!detailModel.isNavigating) {
detailModel.setNavigating(true)
findNavController().navigate( findNavController().navigate(
ArtistDetailFragmentDirections.actionShowAlbum(album.id) ArtistDetailFragmentDirections.actionShowAlbum(data.id)
) )
}
} else if (data is Song) {
playbackModel.playSong(data, PlaybackMode.IN_ARTIST)
} }
}, },
doOnLongClick = { view, data -> doOnLongClick = { view, data ->
@ -57,24 +64,40 @@ class ArtistDetailFragment : DetailFragment() {
} }
) )
// We build the action header here since it's both more efficent to keep one action header
// and it also prevents the header from being constantly refreshed when the sort is updated.
val songsHeader = ActionHeader(
id = -2,
name = getString(R.string.label_songs),
icon = detailModel.artistSortMode.value!!.iconRes,
) { btn ->
detailModel.incrementArtistSortMode()
// We'll update the icon of this header object directly so that the state persists
// after the viewholder is recycled.
icon = detailModel.artistSortMode.value!!.iconRes
btn.setImageResource(icon)
}
// --- UI SETUP --- // --- UI SETUP ---
binding.lifecycleOwner = this binding.lifecycleOwner = this
setupToolbar() setupToolbar()
setupRecycler(detailAdapter) setupRecycler(detailAdapter) { pos ->
// If the item is an ActionHeader we need to also make the item full-width
pos == 0 || detailAdapter.currentList.getOrNull(pos) is ActionHeader
}
detailAdapter.submitList(createData(songsHeader, detailModel.artistSortMode.value!!))
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
detailModel.artistSortMode.observe(viewLifecycleOwner) { mode -> detailModel.artistSortMode.observe(viewLifecycleOwner) { mode ->
logD("Updating sort mode to $mode") logD("Updating sort mode to $mode")
// Header detail data is always included detailAdapter.submitList(createData(songsHeader, mode))
val data = mutableListOf<BaseModel>(detailModel.currentArtist.value!!).also {
it.addAll(mode.getSortedAlbumList(detailModel.currentArtist.value!!.albums))
}
detailAdapter.submitList(data)
} }
detailModel.navToItem.observe(viewLifecycleOwner) { item -> detailModel.navToItem.observe(viewLifecycleOwner) { item ->
@ -105,9 +128,21 @@ class ArtistDetailFragment : DetailFragment() {
// Highlight albums if they are being played // Highlight albums if they are being played
playbackModel.parent.observe(viewLifecycleOwner) { parent -> playbackModel.parent.observe(viewLifecycleOwner) { parent ->
if (parent is Album?) { if (parent is Album?) {
detailAdapter.setCurrentAlbum(parent, binding.detailRecycler) detailAdapter.highlightAlbum(parent, binding.detailRecycler)
} else { } else {
detailAdapter.setCurrentAlbum(null, binding.detailRecycler) detailAdapter.highlightAlbum(null, binding.detailRecycler)
}
}
// Highlight songs if they are being played
playbackModel.song.observe(viewLifecycleOwner) { song ->
if (playbackModel.mode.value == PlaybackMode.IN_ARTIST &&
playbackModel.parent.value?.id == detailModel.currentArtist.value!!.id
) {
detailAdapter.highlightSong(song, binding.detailRecycler)
} else {
// Clear the viewholders if the mode isn't ALL_SONGS
detailAdapter.highlightSong(null, binding.detailRecycler)
} }
} }
@ -115,4 +150,15 @@ class ArtistDetailFragment : DetailFragment() {
return binding.root return binding.root
} }
private fun createData(songHeader: ActionHeader, mode: SortMode): MutableList<BaseModel> {
val artist = detailModel.currentArtist.value!!
val data = mutableListOf<BaseModel>(artist)
data.addAll(SortMode.NUMERIC_DOWN.getSortedAlbumList(artist.albums))
data.add(songHeader)
data.addAll(mode.getSortedArtistSongList(artist.songs))
return data
}
} }

View file

@ -8,10 +8,8 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.fixAnimInfoLeak import org.oxycblt.auxio.ui.fixAnimInfoLeak
import org.oxycblt.auxio.ui.isLandscape import org.oxycblt.auxio.ui.isLandscape
@ -78,7 +76,10 @@ abstract class DetailFragment : Fragment() {
/** /**
* Shortcut method for recyclerview setup * Shortcut method for recyclerview setup
*/ */
protected fun setupRecycler(detailAdapter: ListAdapter<BaseModel, RecyclerView.ViewHolder>) { protected fun setupRecycler(
detailAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>,
gridLookup: (Int) -> Boolean
) {
binding.detailRecycler.apply { binding.detailRecycler.apply {
adapter = detailAdapter adapter = detailAdapter
setHasFixedSize(true) setHasFixedSize(true)
@ -88,7 +89,7 @@ abstract class DetailFragment : Fragment() {
layoutManager = GridLayoutManager(requireContext(), 2).also { layoutManager = GridLayoutManager(requireContext(), 2).also {
it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { it.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int { override fun getSpanSize(position: Int): Int {
return if (position == 0) 2 else 1 return if (gridLookup(position)) 2 else 1
} }
} }
} }

View file

@ -56,7 +56,9 @@ class GenreDetailFragment : DetailFragment() {
binding.lifecycleOwner = this binding.lifecycleOwner = this
setupToolbar() setupToolbar()
setupRecycler(detailAdapter) setupRecycler(detailAdapter) { pos ->
pos == 0
}
// --- DETAILVIEWMODEL SETUP --- // --- DETAILVIEWMODEL SETUP ---

View file

@ -2,21 +2,23 @@ package org.oxycblt.auxio.detail.adapters
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistHeaderBinding import org.oxycblt.auxio.databinding.ItemArtistHeaderBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.databinding.ItemArtistSongBinding
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.DiffCallback import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.ActionHeaderViewHolder
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
import org.oxycblt.auxio.recycler.viewholders.Highlightable import org.oxycblt.auxio.recycler.viewholders.Highlightable
import org.oxycblt.auxio.ui.Accent import org.oxycblt.auxio.ui.Accent
import org.oxycblt.auxio.ui.disable
import org.oxycblt.auxio.ui.inflater import org.oxycblt.auxio.ui.inflater
import org.oxycblt.auxio.ui.setTextColorResource import org.oxycblt.auxio.ui.setTextColorResource
@ -25,19 +27,22 @@ import org.oxycblt.auxio.ui.setTextColorResource
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class ArtistDetailAdapter( class ArtistDetailAdapter(
private val detailModel: DetailViewModel,
private val playbackModel: PlaybackViewModel, private val playbackModel: PlaybackViewModel,
private val lifecycleOwner: LifecycleOwner, private val doOnClick: (data: BaseModel) -> Unit,
private val doOnClick: (data: Album) -> Unit, private val doOnLongClick: (view: View, data: BaseModel) -> Unit,
private val doOnLongClick: (view: View, data: Album) -> Unit,
) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback()) { ) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback()) {
private var currentAlbum: Album? = null private var currentAlbum: Album? = null
private var lastHolder: Highlightable? = null private var currentAlbumHolder: Highlightable? = null
private var currentSong: Song? = null
private var currentSongHolder: Highlightable? = null
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when (getItem(position)) { return when (getItem(position)) {
is Artist -> ARTIST_HEADER_ITEM_TYPE is Artist -> ARTIST_HEADER_ITEM_TYPE
is Album -> ARTIST_ALBUM_ITEM_TYPE is Album -> ARTIST_ALBUM_ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
is Song -> ARTIST_SONG_ITEM_TYPE
else -> -1 else -> -1
} }
@ -53,24 +58,43 @@ class ArtistDetailAdapter(
ItemArtistAlbumBinding.inflate(parent.context.inflater) ItemArtistAlbumBinding.inflate(parent.context.inflater)
) )
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
ARTIST_SONG_ITEM_TYPE -> ArtistSongViewHolder(
ItemArtistSongBinding.inflate(parent.context.inflater)
)
else -> error("Invalid ViewHolder item type $viewType") else -> error("Invalid ViewHolder item type $viewType")
} }
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (val item = getItem(position)) { val item = getItem(position)
when (item) {
is Artist -> (holder as ArtistHeaderViewHolder).bind(item) is Artist -> (holder as ArtistHeaderViewHolder).bind(item)
is Album -> (holder as ArtistAlbumViewHolder).bind(item) is Album -> (holder as ArtistAlbumViewHolder).bind(item)
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
is Song -> (holder as ArtistSongViewHolder).bind(item)
else -> {}
} }
if (currentAlbum != null && position > 0) { if (holder is Highlightable) {
if (getItem(position).id == currentAlbum?.id) { when (item.id) {
// Reset the last ViewHolder before assigning the new, correct one to be highlighted currentAlbum?.id -> {
lastHolder?.setHighlighted(false) currentAlbumHolder?.setHighlighted(false)
lastHolder = (holder as Highlightable) currentAlbumHolder = holder
holder.setHighlighted(true) holder.setHighlighted(true)
} else { }
(holder as Highlightable).setHighlighted(false)
currentSong?.id -> {
currentSongHolder?.setHighlighted(false)
currentSongHolder = holder
holder.setHighlighted(true)
}
else -> holder.setHighlighted(false)
} }
} }
} }
@ -79,11 +103,11 @@ class ArtistDetailAdapter(
* Update the current [album] that this adapter should highlight * Update the current [album] that this adapter should highlight
* @param recycler The recyclerview the highlighting should act on. * @param recycler The recyclerview the highlighting should act on.
*/ */
fun setCurrentAlbum(album: Album?, recycler: RecyclerView) { fun highlightAlbum(album: Album?, recycler: RecyclerView) {
// Clear out the last ViewHolder as a song update usually signifies that this current // Clear out the last ViewHolder as a song update usually signifies that this current
// ViewHolder is likely invalid. // ViewHolder is likely invalid.
lastHolder?.setHighlighted(false) currentAlbumHolder?.setHighlighted(false)
lastHolder = null currentAlbumHolder = null
currentAlbum = album currentAlbum = album
@ -93,12 +117,46 @@ class ArtistDetailAdapter(
item.name == album.name && item is Album item.name == album.name && item is Album
} }
logD(pos)
// Check if the ViewHolder if this album is visible, and highlight it if so. // Check if the ViewHolder if this album is visible, and highlight it if so.
recycler.layoutManager?.findViewByPosition(pos)?.let { child -> recycler.layoutManager?.findViewByPosition(pos)?.let { child ->
recycler.getChildViewHolder(child)?.let { recycler.getChildViewHolder(child)?.let {
lastHolder = it as Highlightable currentAlbumHolder = it as Highlightable
lastHolder?.setHighlighted(true) currentAlbumHolder?.setHighlighted(true)
}
}
}
}
/**
* Update the [song] that this adapter should highlight
* @param recycler The recyclerview the highlighting should act on.
*/
fun highlightSong(song: Song?, recycler: RecyclerView) {
// Clear out the last ViewHolder as a song update usually signifies that this current
// ViewHolder is likely invalid.
currentSongHolder?.setHighlighted(false)
currentSongHolder = null
currentSong = song
if (song != null) {
// Use existing data instead of having to re-sort it.
// We also have to account for the album count when searching for the viewholder
val pos = currentList.indexOfFirst { item ->
item.name == song.name && item is Song
}
// Check if the ViewHolder for this song is visible, if it is then highlight it.
// If the ViewHolder is not visible, then the adapter should take care of it if
// it does become visible.
recycler.layoutManager?.findViewByPosition(pos)?.let { child ->
recycler.getChildViewHolder(child)?.let {
currentSongHolder = it as Highlightable
currentSongHolder?.setHighlighted(true)
} }
} }
} }
@ -110,13 +168,7 @@ class ArtistDetailAdapter(
override fun onBind(data: Artist) { override fun onBind(data: Artist) {
binding.artist = data binding.artist = data
binding.detailModel = detailModel
binding.playbackModel = playbackModel binding.playbackModel = playbackModel
binding.lifecycleOwner = lifecycleOwner
if (data.albums.size < 2) {
binding.artistSortButton.disable()
}
} }
} }
@ -133,6 +185,8 @@ class ArtistDetailAdapter(
} }
override fun setHighlighted(isHighlighted: Boolean) { override fun setHighlighted(isHighlighted: Boolean) {
logD(isHighlighted)
if (isHighlighted) { if (isHighlighted) {
binding.albumName.setTextColorResource(Accent.get().color) binding.albumName.setTextColorResource(Accent.get().color)
} else { } else {
@ -141,8 +195,29 @@ class ArtistDetailAdapter(
} }
} }
inner class ArtistSongViewHolder(
private val binding: ItemArtistSongBinding,
) : BaseViewHolder<Song>(binding, doOnClick, doOnLongClick), Highlightable {
private val normalTextColor = binding.songName.currentTextColor
override fun onBind(data: Song) {
binding.song = data
binding.songName.requestLayout()
}
override fun setHighlighted(isHighlighted: Boolean) {
if (isHighlighted) {
binding.songName.setTextColorResource(Accent.get().color)
} else {
binding.songName.setTextColor(normalTextColor)
}
}
}
companion object { companion object {
const val ARTIST_HEADER_ITEM_TYPE = 0xA009 const val ARTIST_HEADER_ITEM_TYPE = 0xA009
const val ARTIST_ALBUM_ITEM_TYPE = 0xA00A const val ARTIST_ALBUM_ITEM_TYPE = 0xA00A
const val ARTIST_SONG_ITEM_TYPE = 0xA00B
} }
} }

View file

@ -143,7 +143,7 @@ class GenreDetailAdapter(
} }
companion object { companion object {
const val GENRE_HEADER_ITEM_TYPE = 0xA00B const val GENRE_HEADER_ITEM_TYPE = 0xA00C
const val GENRE_SONG_ITEM_TYPE = 0xA00C const val GENRE_SONG_ITEM_TYPE = 0xA00D
} }
} }

View file

@ -199,12 +199,12 @@ data class Header(
/** /**
* A data object for a header with an action button. Inherits [BaseModel]. * A data object for a header with an action button. Inherits [BaseModel].
* @property icon The icon ot apply for this header * @property icon The icon ot apply for this header. This can be changed to reflect any change.
* @property action The callback that will be called when the action button is clicked. * @property action The callback that will be called when the action button is clicked.
*/ */
data class ActionHeader( data class ActionHeader(
override val id: Long = -1, override val id: Long = -1,
override val name: String = "", override val name: String = "",
@DrawableRes val icon: Int, @DrawableRes var icon: Int,
val action: (button: ImageButton) -> Unit, val action: ActionHeader.(button: ImageButton) -> Unit,
) : BaseModel() ) : BaseModel()

View file

@ -6,7 +6,6 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
import org.oxycblt.auxio.databinding.ItemQueueSongBinding import org.oxycblt.auxio.databinding.ItemQueueSongBinding
import org.oxycblt.auxio.logE import org.oxycblt.auxio.logE
import org.oxycblt.auxio.music.ActionHeader import org.oxycblt.auxio.music.ActionHeader
@ -47,9 +46,7 @@ class QueueAdapter(
return when (viewType) { return when (viewType) {
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context) HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder( ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
ItemActionHeaderBinding.inflate(parent.context.inflater)
)
QUEUE_SONG_ITEM_TYPE -> QueueSongViewHolder( QUEUE_SONG_ITEM_TYPE -> QueueSongViewHolder(
ItemQueueSongBinding.inflate(parent.context.inflater) ItemQueueSongBinding.inflate(parent.context.inflater)

View file

@ -186,7 +186,7 @@ class ActionHeaderViewHolder(
setImageResource(data.icon) setImageResource(data.icon)
setOnClickListener { setOnClickListener {
data.action(binding.headerButton) data.action(data, binding.headerButton)
} }
} }
} }
@ -195,11 +195,11 @@ class ActionHeaderViewHolder(
const val ITEM_TYPE = 0xA006 const val ITEM_TYPE = 0xA006
/** /**
* Create an instance of [HeaderViewHolder] * Create an instance of [ActionHeaderViewHolder]
*/ */
fun from(context: Context): HeaderViewHolder { fun from(context: Context): ActionHeaderViewHolder {
return HeaderViewHolder( return ActionHeaderViewHolder(
ItemHeaderBinding.inflate(context.inflater) ItemActionHeaderBinding.inflate(context.inflater)
) )
} }
} }

View file

@ -15,7 +15,6 @@ import org.oxycblt.auxio.music.BaseModel
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.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
/** /**
* Extension method for creating and showing a new [ActionMenu]. * Extension method for creating and showing a new [ActionMenu].
@ -78,6 +77,7 @@ class ActionMenu(
when (flag) { when (flag) {
FLAG_NONE, FLAG_IN_GENRE -> R.menu.menu_song_actions FLAG_NONE, FLAG_IN_GENRE -> R.menu.menu_song_actions
FLAG_IN_ALBUM -> R.menu.menu_album_song_actions FLAG_IN_ALBUM -> R.menu.menu_album_song_actions
FLAG_IN_ARTIST -> R.menu.menu_artist_song_actions
else -> -1 else -> -1
} }
@ -125,12 +125,6 @@ class ActionMenu(
} }
} }
R.id.action_play_artist -> {
if (flag == FLAG_IN_ALBUM && data is Song) {
playbackModel.playSong(data, PlaybackMode.IN_ARTIST)
}
}
R.id.action_queue_add -> { R.id.action_queue_add -> {
when (data) { when (data) {
is Song -> { is Song -> {

View file

@ -107,17 +107,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_play_button" /> app:layout_constraintTop_toBottomOf="@+id/artist_play_button" />
<ImageButton
android:id="@+id/artist_sort_button"
style="@style/HeaderAction"
android:contentDescription="@string/description_sort_button"
android:onClick="@{() -> detailModel.incrementArtistSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/artist_album_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/artist_album_header"
app:sortIcon="@{detailModel.artistSortMode}"
tools:src="@drawable/ic_sort_numeric_down" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -107,17 +107,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_play_button" /> app:layout_constraintTop_toBottomOf="@+id/artist_play_button" />
<ImageButton
android:id="@+id/artist_sort_button"
style="@style/HeaderAction"
android:contentDescription="@string/description_sort_button"
android:onClick="@{() -> detailModel.incrementArtistSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/artist_album_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/artist_album_header"
app:sortIcon="@{detailModel.artistSortMode}"
tools:src="@drawable/ic_sort_numeric_down" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -104,17 +104,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_play_button" /> app:layout_constraintTop_toBottomOf="@+id/artist_play_button" />
<ImageButton
android:id="@+id/artist_sort_button"
style="@style/HeaderAction"
android:contentDescription="@string/description_sort_button"
android:onClick="@{() -> detailModel.incrementArtistSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/artist_album_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/artist_album_header"
app:sortIcon="@{detailModel.artistSortMode}"
tools:src="@drawable/ic_sort_numeric_down" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".recycler.viewholders.SongViewHolder">
<data>
<variable
name="song"
type="org.oxycblt.auxio.music.Song" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout style="@style/ItemSurroundings">
<ImageView
android:id="@+id/album_cover"
android:layout_width="@dimen/size_cover_compact"
android:layout_height="@dimen/size_cover_compact"
android:contentDescription="@{@string/description_album_cover(song.name)}"
app:albumArt="@{song}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_song" />
<TextView
android:id="@+id/song_name"
style="@style/ItemText.Primary"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{song.name}"
app:layout_constraintBottom_toTopOf="@+id/song_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Song Name" />
<TextView
android:id="@+id/song_info"
style="@style/ItemText.Secondary"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{song.album.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toBottomOf="@+id/song_name"
tools:text="Album" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -6,7 +6,4 @@
<item <item
android:id="@+id/action_go_artist" android:id="@+id/action_go_artist"
android:title="@string/label_go_artist" /> android:title="@string/label_go_artist" />
<item
android:id="@+id/action_play_artist"
android:title="@string/label_play_artist" />
</menu> </menu>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_queue_add"
android:title="@string/label_queue_add" />
<item
android:id="@+id/action_go_album"
android:icon="@drawable/ic_album"
android:title="@string/label_go_album" />
</menu>

View file

@ -50,8 +50,9 @@ To prevent any strange bugs, all integer representations must be unique. A table
0xA008 | AlbumSongViewHolder 0xA008 | AlbumSongViewHolder
0xA009 | ArtistHeaderViewHolder 0xA009 | ArtistHeaderViewHolder
0xA00A | ArtistAlbumViewHolder 0xA00A | ArtistAlbumViewHolder
0xA00B | GenreHeaderViewHolder 0xA00B | ArtistSongViewHolder
0xA00C | GenreSongViewHolder 0xA00C | GenreHeaderViewHolder
0xA00D | GenreSongViewHolder
0xA0A0 | Auxio notification code 0xA0A0 | Auxio notification code
0xA0C0 | Auxio request code 0xA0C0 | Auxio request code