detail: refactor module

Completely refactor the detail module. This is for a few reasons:

- Prevent data regeneration every time a fragment re-creates.
- Make DetailModel follow the customs of other ViewModels.
- Simplify layouts into a single detail item to reduce code
complexity.

Currently sorting doesn't work, but that is still being worked out
as the legacy SortMode continues to be phased out of Auxio.
This commit is contained in:
OxygenCobalt 2021-09-11 17:40:19 -06:00
parent d8c0037b10
commit 0e0be19e1d
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
65 changed files with 694 additions and 1740 deletions

View file

@ -139,7 +139,7 @@ class MainActivity : AppCompatActivity() {
systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE View.SYSTEM_UI_FLAG_LAYOUT_STABLE
setOnApplyWindowInsetsListener { v, insets -> setOnApplyWindowInsetsListener { _, insets ->
updatePadding( updatePadding(
left = insets.systemWindowInsetLeft, left = insets.systemWindowInsetLeft,
right = insets.systemWindowInsetRight right = insets.systemWindowInsetRight

View file

@ -106,7 +106,7 @@ data class Accent(
val hex = context.getString(color).uppercase() val hex = context.getString(color).uppercase()
return HtmlCompat.fromHtml( return HtmlCompat.fromHtml(
context.getString(R.string.format_accent_summary, name, hex), context.getString(R.string.fmt_accent_desc, name, hex),
HtmlCompat.FROM_HTML_MODE_COMPACT HtmlCompat.FROM_HTML_MODE_COMPACT
) )
} }

View file

@ -67,6 +67,10 @@ fun ImageView.bindArtistImage(artist: Artist?) {
@BindingAdapter("genreImage") @BindingAdapter("genreImage")
fun ImageView.bindGenreImage(genre: Genre?) { fun ImageView.bindGenreImage(genre: Genre?) {
load(genre, R.drawable.ic_genre, MosaicFetcher(context)) load(genre, R.drawable.ic_genre, MosaicFetcher(context))
if (genre != null) {
contentDescription = context.getString(R.string.desc_genre_image, genre.name)
}
} }
/** /**

View file

@ -27,11 +27,11 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.LinearSmoothScroller
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.adapters.AlbumDetailAdapter import org.oxycblt.auxio.detail.recycler.AlbumDetailAdapter
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.Header
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.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.ActionMenu
@ -52,20 +52,10 @@ class AlbumDetailFragment : DetailFragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
// If DetailViewModel isn't already storing the album, get it from MusicStore detailModel.setAlbum(args.albumId, requireContext())
// using the ID given by the navigation arguments.
if (detailModel.currentAlbum.value == null ||
detailModel.currentAlbum.value?.id != args.albumId
) {
detailModel.updateAlbum(
MusicStore.getInstance().albums.find {
it.id == args.albumId
}!!
)
}
val detailAdapter = AlbumDetailAdapter( val detailAdapter = AlbumDetailAdapter(
detailModel, playbackModel, viewLifecycleOwner, playbackModel, detailModel,
doOnClick = { playbackModel.playSong(it, PlaybackMode.IN_ALBUM) }, doOnClick = { playbackModel.playSong(it, PlaybackMode.IN_ALBUM) },
doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_ALBUM) } doOnLongClick = { view, data -> newMenu(view, data, ActionMenu.FLAG_IN_ALBUM) }
) )
@ -76,7 +66,7 @@ class AlbumDetailFragment : DetailFragment() {
setupToolbar(R.menu.menu_album_detail) { itemId -> setupToolbar(R.menu.menu_album_detail) { itemId ->
if (itemId == R.id.action_queue_add) { if (itemId == R.id.action_queue_add) {
playbackModel.addToUserQueue(detailModel.currentAlbum.value!!) playbackModel.addToUserQueue(detailModel.curAlbum.value!!)
requireContext().showToast(R.string.lbl_queue_added) requireContext().showToast(R.string.lbl_queue_added)
true true
} else { } else {
@ -85,19 +75,13 @@ class AlbumDetailFragment : DetailFragment() {
} }
setupRecycler(detailAdapter) { pos -> setupRecycler(detailAdapter) { pos ->
pos == 0 val item = detailAdapter.currentList[pos]
item is Header || item is ActionHeader || item is Album
} }
// -- DETAILVIEWMODEL SETUP --- // -- DETAILVIEWMODEL SETUP ---
detailModel.albumSortMode.observe(viewLifecycleOwner) { mode -> detailModel.albumData.observe(viewLifecycleOwner) { data ->
logD("Updating sort mode to $mode")
// Detail header data is included
val data = mutableListOf<BaseModel>(detailModel.currentAlbum.value!!).also {
it.addAll(mode.getSortedSongList(detailModel.currentAlbum.value!!.songs))
}
detailAdapter.submitList(data) detailAdapter.submitList(data)
} }
@ -106,10 +90,10 @@ class AlbumDetailFragment : DetailFragment() {
// Songs should be scrolled to if the album matches, or a new detail // Songs should be scrolled to if the album matches, or a new detail
// fragment should be launched otherwise. // fragment should be launched otherwise.
is Song -> { is Song -> {
if (detailModel.currentAlbum.value!!.id == item.album.id) { if (detailModel.curAlbum.value!!.id == item.album.id) {
scrollToItem(item.id) scrollToItem(item.id, detailAdapter)
detailModel.doneWithNavToItem() detailModel.finishNavToItem()
} else { } else {
findNavController().navigate( findNavController().navigate(
AlbumDetailFragmentDirections.actionShowAlbum(item.album.id) AlbumDetailFragmentDirections.actionShowAlbum(item.album.id)
@ -120,9 +104,9 @@ class AlbumDetailFragment : DetailFragment() {
// If the album matches, no need to do anything. Otherwise launch a new // If the album matches, no need to do anything. Otherwise launch a new
// detail fragment. // detail fragment.
is Album -> { is Album -> {
if (detailModel.currentAlbum.value!!.id == item.id) { if (detailModel.curAlbum.value!!.id == item.id) {
binding.detailRecycler.scrollToPosition(0) binding.detailRecycler.scrollToPosition(0)
detailModel.doneWithNavToItem() detailModel.finishNavToItem()
} else { } else {
findNavController().navigate( findNavController().navigate(
AlbumDetailFragmentDirections.actionShowAlbum(item.id) AlbumDetailFragmentDirections.actionShowAlbum(item.id)
@ -146,7 +130,7 @@ class AlbumDetailFragment : DetailFragment() {
playbackModel.song.observe(viewLifecycleOwner) { song -> playbackModel.song.observe(viewLifecycleOwner) { song ->
if (playbackModel.mode.value == PlaybackMode.IN_ALBUM && if (playbackModel.mode.value == PlaybackMode.IN_ALBUM &&
playbackModel.parent.value?.id == detailModel.currentAlbum.value!!.id playbackModel.parent.value?.id == detailModel.curAlbum.value!!.id
) { ) {
detailAdapter.highlightSong(song, binding.detailRecycler) detailAdapter.highlightSong(song, binding.detailRecycler)
} else { } else {
@ -169,17 +153,15 @@ class AlbumDetailFragment : DetailFragment() {
/** /**
* Scroll to an song using its [id]. * Scroll to an song using its [id].
*/ */
private fun scrollToItem(id: Long) { private fun scrollToItem(id: Long, adapter: AlbumDetailAdapter) {
// Calculate where the item for the currently played song is // Calculate where the item for the currently played song is
val pos = detailModel.albumSortMode.value!!.getSortedSongList( val pos = adapter.currentList.indexOfFirst { it.id == id && it is Album }
detailModel.currentAlbum.value!!.songs
).indexOfFirst { it.id == id }
if (pos != -1) { if (pos != -1) {
binding.detailRecycler.post { binding.detailRecycler.post {
// Make sure to increment the position to make up for the detail header // Make sure to increment the position to make up for the detail header
binding.detailRecycler.layoutManager?.startSmoothScroll( binding.detailRecycler.layoutManager?.startSmoothScroll(
CenterSmoothScroller(requireContext(), pos.inc()) CenterSmoothScroller(requireContext(), pos)
) )
// If the recyclerview can scroll, its certain that it will have to scroll to // If the recyclerview can scroll, its certain that it will have to scroll to

View file

@ -24,17 +24,14 @@ 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.recycler.ArtistDetailAdapter
import org.oxycblt.auxio.detail.adapters.ArtistDetailAdapter 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.Header import org.oxycblt.auxio.music.Header
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.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.ActionMenu
import org.oxycblt.auxio.ui.SortMode
import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -50,20 +47,10 @@ class ArtistDetailFragment : DetailFragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
// If DetailViewModel isn't already storing the artist, get it from MusicStore detailModel.setArtist(args.artistId, requireContext())
// using the ID given by the navigation arguments
if (detailModel.currentArtist.value == null ||
detailModel.currentArtist.value?.id != args.artistId
) {
detailModel.updateArtist(
MusicStore.getInstance().artists.find {
it.id == args.artistId
}!!
)
}
val detailAdapter = ArtistDetailAdapter( val detailAdapter = ArtistDetailAdapter(
playbackModel, detailModel, playbackModel,
doOnClick = { data -> doOnClick = { data ->
if (!detailModel.isNavigating) { if (!detailModel.isNavigating) {
detailModel.setNavigating(true) detailModel.setNavigating(true)
@ -88,39 +75,22 @@ class ArtistDetailFragment : DetailFragment() {
setupToolbar() setupToolbar()
setupRecycler(detailAdapter) { pos -> setupRecycler(detailAdapter) { pos ->
// If the item is an ActionHeader we need to also make the item full-width // If the item is an ActionHeader we need to also make the item full-width
pos == 0 || detailAdapter.currentList.getOrNull(pos) is Header val item = detailAdapter.currentList[pos]
item is Header || item is ActionHeader || item is Artist
} }
// --- VIEWMODEL SETUP --- // --- VIEWMODEL SETUP ---
detailModel.artistSortMode.observe(viewLifecycleOwner) { mode -> detailModel.artistData.observe(viewLifecycleOwner) { data ->
logD("Updating sort mode to $mode")
val artist = detailModel.currentArtist.value!!
val data = mutableListOf<BaseModel>(artist)
data.addAll(SortMode.NUMERIC_DOWN.getSortedAlbumList(artist.albums))
data.add(
Header(
id = -2,
name = getString(R.string.lbl_songs),
isAction = true
)
)
data.addAll(mode.getSortedArtistSongList(artist.songs))
detailAdapter.submitList(data) detailAdapter.submitList(data)
} }
detailModel.navToItem.observe(viewLifecycleOwner) { item -> detailModel.navToItem.observe(viewLifecycleOwner) { item ->
when (item) { when (item) {
is Artist -> { is Artist -> {
if (item.id == detailModel.currentArtist.value!!.id) { if (item.id == detailModel.curArtist.value?.id) {
binding.detailRecycler.scrollToPosition(0) binding.detailRecycler.scrollToPosition(0)
detailModel.doneWithNavToItem() detailModel.finishNavToItem()
} else { } else {
findNavController().navigate( findNavController().navigate(
ArtistDetailFragmentDirections.actionShowArtist(item.id) ArtistDetailFragmentDirections.actionShowArtist(item.id)
@ -136,7 +106,8 @@ class ArtistDetailFragment : DetailFragment() {
ArtistDetailFragmentDirections.actionShowAlbum(item.album.id) ArtistDetailFragmentDirections.actionShowAlbum(item.album.id)
) )
else -> {} else -> {
}
} }
} }
@ -152,7 +123,7 @@ class ArtistDetailFragment : DetailFragment() {
// Highlight songs if they are being played // Highlight songs if they are being played
playbackModel.song.observe(viewLifecycleOwner) { song -> playbackModel.song.observe(viewLifecycleOwner) { song ->
if (playbackModel.mode.value == PlaybackMode.IN_ARTIST && if (playbackModel.mode.value == PlaybackMode.IN_ARTIST &&
playbackModel.parent.value?.id == detailModel.currentArtist.value!!.id playbackModel.parent.value?.id == detailModel.curArtist.value?.id
) { ) {
detailAdapter.highlightSong(song, binding.detailRecycler) detailAdapter.highlightSong(song, binding.detailRecycler)
} else { } else {

View file

@ -36,9 +36,6 @@ import org.oxycblt.auxio.util.isLandscape
/** /**
* A Base [Fragment] implementing the base features shared across all detail fragments. * A Base [Fragment] implementing the base features shared across all detail fragments.
* TODO: Want to implement something using CollapsingToolbarLayout. This would eliminate alot of
* the complexity from this ball of mud, but I have to do something to fix the scroll stopping
* issue.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
abstract class DetailFragment : Fragment() { abstract class DetailFragment : Fragment() {

View file

@ -18,112 +18,135 @@
package org.oxycblt.auxio.detail package org.oxycblt.auxio.detail
import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.LibSortMode
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.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.ui.SortMode import org.oxycblt.auxio.ui.SortMode
/** /**
* ViewModel that stores data for the [DetailFragment]s, such as what they're showing & what * ViewModel that stores data for the [DetailFragment]s, such as what they're showing & what
* [SortMode] they are currently on. * [SortMode] they are currently on.
* TODO: Redo sorting here * TODO: Re-add sorting
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class DetailViewModel : ViewModel() { class DetailViewModel : ViewModel() {
private val settingsManager = SettingsManager.getInstance()
// --- CURRENT VALUES --- // --- CURRENT VALUES ---
private val mCurrentGenre = MutableLiveData<Genre?>() private val mCurGenre = MutableLiveData<Genre?>()
val currentGenre: LiveData<Genre?> get() = mCurrentGenre val curGenre: LiveData<Genre?> get() = mCurGenre
private val mCurrentArtist = MutableLiveData<Artist?>() private val mGenreData = MutableLiveData(listOf<BaseModel>())
val currentArtist: LiveData<Artist?> get() = mCurrentArtist val genreData: LiveData<List<BaseModel>> = mGenreData
private val mCurrentAlbum = MutableLiveData<Album?>() private val mCurArtist = MutableLiveData<Artist?>()
val currentAlbum: LiveData<Album?> get() = mCurrentAlbum val curArtist: LiveData<Artist?> get() = mCurArtist
// --- SORT MODES --- private val mArtistData = MutableLiveData(listOf<BaseModel>())
val artistData: LiveData<List<BaseModel>> = mArtistData
private val mGenreSortMode = MutableLiveData(settingsManager.genreSortMode) private val mCurAlbum = MutableLiveData<Album?>()
val genreSortMode: LiveData<SortMode> get() = mGenreSortMode val curAlbum: LiveData<Album?> get() = mCurAlbum
private val mArtistSortMode = MutableLiveData(settingsManager.artistSortMode) private val mAlbumData = MutableLiveData(listOf<BaseModel>())
val albumSortMode: LiveData<SortMode> get() = mAlbumSortMode val albumData: LiveData<List<BaseModel>> get() = mAlbumData
private val mAlbumSortMode = MutableLiveData(settingsManager.albumSortMode) var isNavigating = false
val artistSortMode: LiveData<SortMode> get() = mArtistSortMode private set
private var mIsNavigating = false
val isNavigating: Boolean get() = mIsNavigating
private val mNavToItem = MutableLiveData<BaseModel?>() private val mNavToItem = MutableLiveData<BaseModel?>()
/** Flag for unified navigation. Observe this to coordinate navigation to an item's UI. */ /** Flag for unified navigation. Observe this to coordinate navigation to an item's UI. */
val navToItem: LiveData<BaseModel?> get() = mNavToItem val navToItem: LiveData<BaseModel?> get() = mNavToItem
fun updateGenre(genre: Genre) { private val musicStore = MusicStore.getInstance()
mCurrentGenre.value = genre
fun setGenre(id: Long, context: Context) {
if (mCurGenre.value?.id == id) return
mCurGenre.value = musicStore.genres.find { it.id == id }
val data = mutableListOf<BaseModel>(curGenre.value!!)
data.add(
ActionHeader(
id = -2,
name = context.getString(R.string.lbl_songs),
icon = R.drawable.ic_sort,
desc = R.string.lbl_sort,
onClick = {
}
)
)
data.addAll(LibSortMode.ASCENDING.sortGenre(curGenre.value!!))
mGenreData.value = data
} }
fun updateArtist(artist: Artist) { fun setArtist(id: Long, context: Context) {
mCurrentArtist.value = artist if (mCurArtist.value?.id == id) return
mCurArtist.value = musicStore.artists.find { it.id == id }
val artist = curArtist.value!!
val data = mutableListOf<BaseModel>(artist)
data.add(
Header(
id = -2,
name = context.getString(R.string.lbl_albums)
)
)
data.addAll(LibSortMode.YEAR.sortAlbums(artist.albums))
data.add(
ActionHeader(
id = -3,
name = context.getString(R.string.lbl_songs),
icon = R.drawable.ic_sort,
desc = R.string.lbl_sort,
onClick = {
}
)
)
data.addAll(LibSortMode.YEAR.sortArtist(artist))
mArtistData.value = data.toList()
} }
fun updateAlbum(album: Album) { fun setAlbum(id: Long, context: Context) {
mCurrentAlbum.value = album if (mCurAlbum.value?.id == id) return
}
/** mCurAlbum.value = musicStore.albums.find { it.id == id }
* Increment the sort mode of the genre artists
*/
fun incrementGenreSortMode() {
val mode = when (mGenreSortMode.value) {
SortMode.ALPHA_DOWN -> SortMode.ALPHA_UP
SortMode.ALPHA_UP -> SortMode.ALPHA_DOWN
else -> SortMode.ALPHA_DOWN val data = mutableListOf<BaseModel>(curAlbum.value!!)
}
mGenreSortMode.value = mode data.add(
settingsManager.genreSortMode = mode ActionHeader(
} id = -2,
name = context.getString(R.string.lbl_songs),
icon = R.drawable.ic_sort,
desc = R.string.lbl_sort,
onClick = {
}
)
)
/** data.addAll(LibSortMode.ASCENDING.sortAlbum(curAlbum.value!!))
* Increment the sort mode of the artist albums
*/
fun incrementArtistSortMode() {
val mode = when (mArtistSortMode.value) {
SortMode.NUMERIC_DOWN -> SortMode.NUMERIC_UP
SortMode.NUMERIC_UP -> SortMode.ALPHA_DOWN
SortMode.ALPHA_DOWN -> SortMode.ALPHA_UP
SortMode.ALPHA_UP -> SortMode.NUMERIC_DOWN
else -> SortMode.NUMERIC_DOWN mAlbumData.value = data
}
mArtistSortMode.value = mode
settingsManager.artistSortMode = mode
}
/**
* Increment the sort mode of the album song
*/
fun incrementAlbumSortMode() {
val mode = when (mAlbumSortMode.value) {
SortMode.NUMERIC_DOWN -> SortMode.NUMERIC_UP
SortMode.NUMERIC_UP -> SortMode.NUMERIC_DOWN
else -> SortMode.NUMERIC_DOWN
}
mAlbumSortMode.value = mode
settingsManager.albumSortMode = mAlbumSortMode.value!!
} }
/** /**
@ -136,14 +159,14 @@ class DetailViewModel : ViewModel() {
/** /**
* Mark that the navigation process is done. * Mark that the navigation process is done.
*/ */
fun doneWithNavToItem() { fun finishNavToItem() {
mNavToItem.value = null mNavToItem.value = null
} }
/** /**
* Update the current navigation status to [isNavigating] * Update the current navigation status to [isNavigating]
*/ */
fun setNavigating(isNavigating: Boolean) { fun setNavigating(navigating: Boolean) {
mIsNavigating = isNavigating isNavigating = navigating
} }
} }

View file

@ -24,11 +24,12 @@ 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.detail.adapters.GenreDetailAdapter import org.oxycblt.auxio.detail.recycler.GenreDetailAdapter
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.Genre
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.ActionMenu import org.oxycblt.auxio.ui.ActionMenu
@ -47,20 +48,10 @@ class GenreDetailFragment : DetailFragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
// If DetailViewModel isn't already storing the genre, get it from MusicStore detailModel.setGenre(args.genreId, requireContext())
// using the ID given by the navigation arguments
if (detailModel.currentGenre.value == null ||
detailModel.currentGenre.value?.id != args.genreId
) {
detailModel.updateGenre(
MusicStore.getInstance().genres.find {
it.id == args.genreId
}!!
)
}
val detailAdapter = GenreDetailAdapter( val detailAdapter = GenreDetailAdapter(
detailModel, playbackModel, viewLifecycleOwner, playbackModel,
doOnClick = { song -> doOnClick = { song ->
playbackModel.playSong(song, PlaybackMode.IN_GENRE) playbackModel.playSong(song, PlaybackMode.IN_GENRE)
}, },
@ -75,19 +66,13 @@ class GenreDetailFragment : DetailFragment() {
setupToolbar() setupToolbar()
setupRecycler(detailAdapter) { pos -> setupRecycler(detailAdapter) { pos ->
pos == 0 val item = detailAdapter.currentList[pos]
item is Header || item is ActionHeader || item is Genre
} }
// --- DETAILVIEWMODEL SETUP --- // --- DETAILVIEWMODEL SETUP ---
detailModel.genreSortMode.observe(viewLifecycleOwner) { mode -> detailModel.genreData.observe(viewLifecycleOwner) { data ->
logD("Updating sort mode to $mode")
// Detail header data is included
val data = mutableListOf<BaseModel>(detailModel.currentGenre.value!!).also {
it.addAll(mode.getSortedSongList(detailModel.currentGenre.value!!.songs))
}
detailAdapter.submitList(data) detailAdapter.submitList(data)
} }
@ -106,7 +91,8 @@ class GenreDetailFragment : DetailFragment() {
GenreDetailFragmentDirections.actionShowAlbum(item.album.id) GenreDetailFragmentDirections.actionShowAlbum(item.album.id)
) )
else -> {} else -> {
}
} }
} }
@ -114,7 +100,7 @@ class GenreDetailFragment : DetailFragment() {
playbackModel.song.observe(viewLifecycleOwner) { song -> playbackModel.song.observe(viewLifecycleOwner) { song ->
if (playbackModel.mode.value == PlaybackMode.IN_GENRE && if (playbackModel.mode.value == PlaybackMode.IN_GENRE &&
playbackModel.parent.value?.id == detailModel.currentGenre.value!!.id playbackModel.parent.value?.id == detailModel.curGenre.value!!.id
) { ) {
detailAdapter.highlightSong(song, binding.detailRecycler) detailAdapter.highlightSong(song, binding.detailRecycler)
} else { } else {

View file

@ -16,23 +16,26 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.detail.adapters package org.oxycblt.auxio.detail.recycler
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.ItemAlbumHeaderBinding import org.oxycblt.auxio.R
import org.oxycblt.auxio.coil.bindAlbumArt
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
import org.oxycblt.auxio.databinding.ItemDetailBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
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.ui.ActionHeaderViewHolder
import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.util.disable import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
/** /**
@ -40,9 +43,8 @@ import org.oxycblt.auxio.util.inflater
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class AlbumDetailAdapter( class AlbumDetailAdapter(
private val detailModel: DetailViewModel,
private val playbackModel: PlaybackViewModel, private val playbackModel: PlaybackViewModel,
private val lifecycleOwner: LifecycleOwner, private val detailModel: DetailViewModel,
private val doOnClick: (data: Song) -> Unit, private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (view: View, data: Song) -> Unit private val doOnLongClick: (view: View, data: Song) -> Unit
) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback()) { ) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback()) {
@ -52,20 +54,25 @@ class AlbumDetailAdapter(
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when (getItem(position)) { return when (getItem(position)) {
is Album -> ALBUM_HEADER_ITEM_TYPE is Album -> ALBUM_HEADER_ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
is Song -> ALBUM_SONG_ITEM_TYPE is Song -> ALBUM_SONG_ITEM_TYPE
else -> -1 else -> -1
} }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) { return when (viewType) {
ALBUM_HEADER_ITEM_TYPE -> AlbumHeaderViewHolder( ALBUM_HEADER_ITEM_TYPE -> AlbumHeaderViewHolder(
ItemAlbumHeaderBinding.inflate(parent.context.inflater) ItemDetailBinding.inflate(parent.context.inflater)
) )
ALBUM_SONG_ITEM_TYPE -> AlbumSongViewHolder( ALBUM_SONG_ITEM_TYPE -> AlbumSongViewHolder(
ItemAlbumSongBinding.inflate(parent.context.inflater) ItemAlbumSongBinding.inflate(parent.context.inflater)
) )
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
else -> error("Invalid ViewHolder item type $viewType") else -> error("Invalid ViewHolder item type $viewType")
} }
} }
@ -76,18 +83,20 @@ class AlbumDetailAdapter(
when (item) { when (item) {
is Album -> (holder as AlbumHeaderViewHolder).bind(item) is Album -> (holder as AlbumHeaderViewHolder).bind(item)
is Song -> (holder as AlbumSongViewHolder).bind(item) is Song -> (holder as AlbumSongViewHolder).bind(item)
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
else -> {} else -> {
}
} }
if (currentSong != null && position > 0) { if (holder is Highlightable) {
if (item.id == currentSong?.id) { if (item.id == currentSong?.id) {
// Reset the last ViewHolder before assigning the new, correct one to be highlighted // Reset the last ViewHolder before assigning the new, correct one to be highlighted
currentHolder?.setHighlighted(false) currentHolder?.setHighlighted(false)
currentHolder = (holder as Highlightable) currentHolder = holder
holder.setHighlighted(true) holder.setHighlighted(true)
} else { } else {
(holder as Highlightable).setHighlighted(false) holder.setHighlighted(false)
} }
} }
} }
@ -124,17 +133,41 @@ class AlbumDetailAdapter(
} }
inner class AlbumHeaderViewHolder( inner class AlbumHeaderViewHolder(
private val binding: ItemAlbumHeaderBinding private val binding: ItemDetailBinding
) : BaseViewHolder<Album>(binding) { ) : BaseViewHolder<Album>(binding) {
override fun onBind(data: Album) { override fun onBind(data: Album) {
binding.album = data binding.detailCover.apply {
binding.detailModel = detailModel bindAlbumArt(data)
binding.playbackModel = playbackModel contentDescription = context.getString(R.string.desc_album_cover, data.name)
binding.lifecycleOwner = lifecycleOwner }
if (data.songs.size < 2) { binding.detailName.text = data.name
binding.albumSortButton.disable()
binding.detailSubhead.apply {
text = data.artist.name
setOnClickListener {
detailModel.navToItem(data)
}
}
binding.detailInfo.text = binding.detailInfo.context.getString(
R.string.fmt_three,
data.year.toString(),
binding.detailInfo.context.getPlural(
R.plurals.fmt_song_count,
data.songs.size
),
data.totalDuration
)
binding.detailPlayButton.setOnClickListener {
playbackModel.playAlbum(data, false)
}
binding.detailShuffleButton.setOnClickListener {
playbackModel.playAlbum(data, true)
} }
} }
} }

View file

@ -16,36 +16,37 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.detail.adapters package org.oxycblt.auxio.detail.recycler
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
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.ItemActionHeaderBinding import org.oxycblt.auxio.R
import org.oxycblt.auxio.coil.bindArtistImage
import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistHeaderBinding
import org.oxycblt.auxio.databinding.ItemArtistSongBinding import org.oxycblt.auxio.databinding.ItemArtistSongBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.databinding.ItemDetailBinding
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.Header import org.oxycblt.auxio.music.Header
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.ui.ActionHeaderViewHolder
import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.util.disable import org.oxycblt.auxio.ui.HeaderViewHolder
import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
/** /**
* An adapter for displaying the [Album]s and [Song]s of an artist. * An adapter for displaying the [Album]s and [Song]s of an artist.
* This isnt the nicest implementation, but it works.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class ArtistDetailAdapter( class ArtistDetailAdapter(
private val playbackModel: PlaybackViewModel, private val playbackModel: PlaybackViewModel,
private val detailModel: DetailViewModel,
private val doOnClick: (data: Album) -> Unit, private val doOnClick: (data: Album) -> Unit,
private val doOnSongClick: (data: Song) -> Unit, private val doOnSongClick: (data: Song) -> Unit,
private val doOnLongClick: (view: View, data: BaseModel) -> Unit, private val doOnLongClick: (view: View, data: BaseModel) -> Unit,
@ -60,8 +61,9 @@ class ArtistDetailAdapter(
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 Header -> ARTIST_SONG_HEADER_ITEM_TYPE
is Song -> ARTIST_SONG_ITEM_TYPE is Song -> ARTIST_SONG_ITEM_TYPE
is Header -> HeaderViewHolder.ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
else -> -1 else -> -1
} }
@ -70,21 +72,21 @@ class ArtistDetailAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) { return when (viewType) {
ARTIST_HEADER_ITEM_TYPE -> ArtistHeaderViewHolder( ARTIST_HEADER_ITEM_TYPE -> ArtistHeaderViewHolder(
ItemArtistHeaderBinding.inflate(parent.context.inflater) ItemDetailBinding.inflate(parent.context.inflater)
) )
ARTIST_ALBUM_ITEM_TYPE -> ArtistAlbumViewHolder( ARTIST_ALBUM_ITEM_TYPE -> ArtistAlbumViewHolder(
ItemArtistAlbumBinding.inflate(parent.context.inflater) ItemArtistAlbumBinding.inflate(parent.context.inflater)
) )
ARTIST_SONG_HEADER_ITEM_TYPE -> ArtistSongHeaderViewHolder(
ItemActionHeaderBinding.inflate(parent.context.inflater)
)
ARTIST_SONG_ITEM_TYPE -> ArtistSongViewHolder( ARTIST_SONG_ITEM_TYPE -> ArtistSongViewHolder(
ItemArtistSongBinding.inflate(parent.context.inflater) ItemArtistSongBinding.inflate(parent.context.inflater)
) )
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
else -> error("Invalid ViewHolder item type $viewType") else -> error("Invalid ViewHolder item type $viewType")
} }
} }
@ -95,9 +97,9 @@ class ArtistDetailAdapter(
when (item) { 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 Header -> (holder as ArtistSongHeaderViewHolder).bind(item)
is Song -> (holder as ArtistSongViewHolder).bind(item) is Song -> (holder as ArtistSongViewHolder).bind(item)
is Header -> (holder as HeaderViewHolder).bind(item)
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
else -> { else -> {
} }
} }
@ -180,22 +182,43 @@ class ArtistDetailAdapter(
} }
inner class ArtistHeaderViewHolder( inner class ArtistHeaderViewHolder(
private val binding: ItemArtistHeaderBinding private val binding: ItemDetailBinding
) : BaseViewHolder<Artist>(binding) { ) : BaseViewHolder<Artist>(binding) {
override fun onBind(data: Artist) { override fun onBind(data: Artist) {
binding.artist = data val context = binding.root.context
binding.playbackModel = playbackModel
binding.detailCover.apply {
bindArtistImage(data)
contentDescription = context.getString(R.string.desc_artist_image, data.name)
}
binding.detailName.text = data.name
binding.detailSubhead.text = data.genre?.resolvedName
?: context.getString(R.string.def_genre)
binding.detailInfo.text = context.getString(
R.string.fmt_counts,
context.getPlural(R.plurals.fmt_album_count, data.albums.size),
context.getPlural(R.plurals.fmt_song_count, data.songs.size)
)
binding.detailPlayButton.setOnClickListener {
playbackModel.playArtist(data, false)
}
binding.detailShuffleButton.setOnClickListener {
playbackModel.playArtist(data, true)
}
} }
} }
// Generic ViewHolder for a detail album
inner class ArtistAlbumViewHolder( inner class ArtistAlbumViewHolder(
private val binding: ItemArtistAlbumBinding, private val binding: ItemArtistAlbumBinding,
) : BaseViewHolder<Album>(binding, doOnClick, doOnLongClick), Highlightable { ) : BaseViewHolder<Album>(binding, doOnClick, doOnLongClick), Highlightable {
override fun onBind(data: Album) { override fun onBind(data: Album) {
binding.album = data binding.album = data
binding.albumName.requestLayout() binding.albumName.requestLayout()
} }
@ -204,39 +227,11 @@ class ArtistDetailAdapter(
} }
} }
inner class ArtistSongHeaderViewHolder(
private val binding: ItemActionHeaderBinding
) : BaseViewHolder<Header>(binding) {
override fun onBind(data: Header) {
binding.header = data
binding.headerButton.apply {
val sortMode = detailModel.artistSortMode
val artist = detailModel.currentArtist.value!!
setImageResource(sortMode.value!!.iconRes)
setOnClickListener {
detailModel.incrementArtistSortMode()
setImageResource(sortMode.value!!.iconRes)
}
if (artist.songs.size < 2) {
disable()
}
}
}
}
inner class ArtistSongViewHolder( inner class ArtistSongViewHolder(
private val binding: ItemArtistSongBinding, private val binding: ItemArtistSongBinding,
) : BaseViewHolder<Song>(binding, doOnSongClick, doOnLongClick), Highlightable { ) : BaseViewHolder<Song>(binding, doOnSongClick, doOnLongClick), Highlightable {
private val normalTextColor = binding.songName.currentTextColor
override fun onBind(data: Song) { override fun onBind(data: Song) {
binding.song = data binding.song = data
binding.songName.requestLayout() binding.songName.requestLayout()
} }
@ -248,7 +243,6 @@ class ArtistDetailAdapter(
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_HEADER_ITEM_TYPE = 0xA00B
const val ARTIST_SONG_ITEM_TYPE = 0xA00C const val ARTIST_SONG_ITEM_TYPE = 0xA00C
} }
} }

View file

@ -16,23 +16,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.detail.adapters package org.oxycblt.auxio.detail.recycler
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.ItemGenreHeaderBinding import org.oxycblt.auxio.R
import org.oxycblt.auxio.coil.bindGenreImage
import org.oxycblt.auxio.databinding.ItemDetailBinding
import org.oxycblt.auxio.databinding.ItemGenreSongBinding import org.oxycblt.auxio.databinding.ItemGenreSongBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.BaseModel 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.ui.ActionHeaderViewHolder
import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.util.disable import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
/** /**
@ -40,9 +42,7 @@ import org.oxycblt.auxio.util.inflater
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class GenreDetailAdapter( class GenreDetailAdapter(
private val detailModel: DetailViewModel,
private val playbackModel: PlaybackViewModel, private val playbackModel: PlaybackViewModel,
private val lifecycleOwner: LifecycleOwner,
private val doOnClick: (data: Song) -> Unit, private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (view: View, data: Song) -> Unit private val doOnLongClick: (view: View, data: Song) -> Unit
) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback()) { ) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback()) {
@ -52,6 +52,7 @@ class GenreDetailAdapter(
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when (getItem(position)) { return when (getItem(position)) {
is Genre -> GENRE_HEADER_ITEM_TYPE is Genre -> GENRE_HEADER_ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
is Song -> GENRE_SONG_ITEM_TYPE is Song -> GENRE_SONG_ITEM_TYPE
else -> -1 else -> -1
@ -61,13 +62,15 @@ class GenreDetailAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) { return when (viewType) {
GENRE_HEADER_ITEM_TYPE -> GenreHeaderViewHolder( GENRE_HEADER_ITEM_TYPE -> GenreHeaderViewHolder(
ItemGenreHeaderBinding.inflate(parent.context.inflater) ItemDetailBinding.inflate(parent.context.inflater)
) )
GENRE_SONG_ITEM_TYPE -> GenreSongViewHolder( GENRE_SONG_ITEM_TYPE -> GenreSongViewHolder(
ItemGenreSongBinding.inflate(parent.context.inflater), ItemGenreSongBinding.inflate(parent.context.inflater),
) )
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
else -> error("Bad viewholder item type $viewType") else -> error("Bad viewholder item type $viewType")
} }
} }
@ -77,18 +80,19 @@ class GenreDetailAdapter(
when (item) { when (item) {
is Genre -> (holder as GenreHeaderViewHolder).bind(item) is Genre -> (holder as GenreHeaderViewHolder).bind(item)
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
is Song -> (holder as GenreSongViewHolder).bind(item) is Song -> (holder as GenreSongViewHolder).bind(item)
else -> {} else -> {}
} }
if (currentSong != null && position > 0) { if (holder is Highlightable) {
if (item.id == currentSong?.id) { if (item.id == currentSong?.id) {
// Reset the last ViewHolder before assigning the new, correct one to be highlighted // Reset the last ViewHolder before assigning the new, correct one to be highlighted
currentHolder?.setHighlighted(false) currentHolder?.setHighlighted(false)
currentHolder = (holder as Highlightable) currentHolder = holder
holder.setHighlighted(true) holder.setHighlighted(true)
} else { } else {
(holder as Highlightable).setHighlighted(false) holder.setHighlighted(false)
} }
} }
} }
@ -125,16 +129,30 @@ class GenreDetailAdapter(
} }
inner class GenreHeaderViewHolder( inner class GenreHeaderViewHolder(
private val binding: ItemGenreHeaderBinding private val binding: ItemDetailBinding
) : BaseViewHolder<Genre>(binding) { ) : BaseViewHolder<Genre>(binding) {
override fun onBind(data: Genre) { override fun onBind(data: Genre) {
binding.genre = data val context = binding.root.context
binding.detailModel = detailModel
binding.playbackModel = playbackModel
binding.lifecycleOwner = lifecycleOwner
if (data.songs.size < 2) { binding.detailCover.apply {
binding.genreSortButton.disable() bindGenreImage(data)
contentDescription = context.getString(R.string.desc_artist_image, data.name)
}
binding.detailName.text = data.name
binding.detailSubhead.apply {
text = context.getPlural(R.plurals.fmt_song_count, data.songs.size)
}
binding.detailInfo.text = data.totalDuration
binding.detailPlayButton.setOnClickListener {
playbackModel.playGenre(data, false)
}
binding.detailShuffleButton.setOnClickListener {
playbackModel.playGenre(data, true)
} }
} }
} }

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.detail.adapters package org.oxycblt.auxio.detail.recycler
/** /**
* Interface that allows the highlighting of certain ViewHolders * Interface that allows the highlighting of certain ViewHolders

View file

@ -25,7 +25,7 @@ import org.oxycblt.auxio.databinding.ItemExcludedDirBinding
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
/** /**
* Adapter that shows the blacklist entries and their "Clear" button. * Adapter that shows the excluded directories and their "Clear" button.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class ExcludedEntryAdapter( class ExcludedEntryAdapter(

View file

@ -21,7 +21,9 @@ package org.oxycblt.auxio.home
import androidx.annotation.IdRes import androidx.annotation.IdRes
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.sliceArticle import org.oxycblt.auxio.ui.sliceArticle
@ -130,6 +132,22 @@ enum class LibSortMode(@IdRes val itemId: Int) {
} }
} }
/**
* Sort the songs in an artist.
* @see sortSongs
*/
fun sortArtist(artist: Artist): List<Song> {
return sortSongs(artist.songs)
}
/**
* Sort the songs in a genre.
* @see sortSongs
*/
fun sortGenre(genre: Genre): List<Song> {
return sortSongs(genre.songs)
}
companion object { companion object {
/** /**
* Convert a menu [id] to an instance of [LibSortMode]. * Convert a menu [id] to an instance of [LibSortMode].

View file

@ -19,6 +19,9 @@
package org.oxycblt.auxio.music package org.oxycblt.auxio.music
import android.net.Uri import android.net.Uri
import android.view.View
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
// --- MUSIC MODELS --- // --- MUSIC MODELS ---
@ -218,11 +221,21 @@ data class Genre(
} }
/** /**
* A data object used solely for the "Header" UI element. Inherits [BaseModel]. * A data object used solely for the "Header" UI element.
* @param isAction Whether this header corresponds to an action or not
*/ */
data class Header( data class Header(
override val id: Long, override val id: Long,
override val name: String, override val name: String,
val isAction: Boolean = false ) : BaseModel()
/**
* A data object used for an action header. Like [Header], but with a button.
* Inherits [BaseModel].
*/
data class ActionHeader(
override val id: Long,
override val name: String,
@DrawableRes val icon: Int,
@StringRes val desc: Int,
val onClick: (View) -> Unit
) : BaseModel() ) : BaseModel()

View file

@ -19,7 +19,6 @@
package org.oxycblt.auxio.music package org.oxycblt.auxio.music
import android.content.ContentUris import android.content.ContentUris
import android.content.Context
import android.net.Uri import android.net.Uri
import android.provider.MediaStore import android.provider.MediaStore
import android.text.format.DateUtils import android.text.format.DateUtils
@ -122,67 +121,16 @@ fun Long.toDuration(): String {
return durationString return durationString
} }
/**
* Convert an integer to its formatted year.
*/
fun Int.toYear(context: Context): String {
return if (this > 0) {
toString()
} else {
context.getString(R.string.def_date)
}
}
// --- BINDING ADAPTERS --- // --- BINDING ADAPTERS ---
/**
* Bind the most prominent artist genre
*/
@BindingAdapter("artistGenre")
fun TextView.bindArtistGenre(artist: Artist) {
text = artist.genre?.resolvedName ?: context.getString(R.string.def_genre)
}
/** /**
* Bind the album + song counts for an artist * Bind the album + song counts for an artist
*/ */
@BindingAdapter("artistCounts") @BindingAdapter("artistCounts")
fun TextView.bindArtistCounts(artist: Artist) { fun TextView.bindArtistCounts(artist: Artist) {
val albums = context.getPlural(R.plurals.fmt_album_count, artist.albums.size)
val songs = context.getPlural(R.plurals.fmt_song_count, artist.songs.size)
text = context.getString(R.string.format_double_counts, albums, songs)
}
/**
* Get all album information, used on [org.oxycblt.auxio.detail.AlbumDetailFragment]
*/
@BindingAdapter("albumDetails")
fun TextView.bindAllAlbumDetails(album: Album) {
text = context.getString( text = context.getString(
R.string.format_double_info, R.string.fmt_counts,
album.year.toYear(context), context.getPlural(R.plurals.fmt_album_count, artist.albums.size),
context.getPlural(R.plurals.fmt_song_count, album.songs.size), context.getPlural(R.plurals.fmt_song_count, artist.songs.size)
album.totalDuration
) )
} }
/**
* Get basic information about an album, used on album ViewHolders
*/
@BindingAdapter("albumInfo")
fun TextView.bindAlbumInfo(album: Album) {
text = context.getString(
R.string.format_info,
album.artist.name,
context.getPlural(R.plurals.fmt_song_count, album.songs.size),
)
}
/**
* Bind the year for an album.
*/
@BindingAdapter("albumYear")
fun TextView.bindAlbumYear(album: Album) {
text = album.year.toYear(context)
}

View file

@ -129,11 +129,11 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
} }
} }
playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { nextQueue -> playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) {
updateQueueIcon(queueItem) updateQueueIcon(queueItem)
} }
playbackModel.userQueue.observe(viewLifecycleOwner) { userQueue -> playbackModel.userQueue.observe(viewLifecycleOwner) {
updateQueueIcon(queueItem) updateQueueIcon(queueItem)
} }

View file

@ -21,17 +21,16 @@ package org.oxycblt.auxio.playback.queue
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.MotionEvent import android.view.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.TooltipCompat
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.R
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
import org.oxycblt.auxio.databinding.ItemQueueSongBinding import org.oxycblt.auxio.databinding.ItemQueueSongBinding
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Header
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.ui.ActionHeaderViewHolder
import org.oxycblt.auxio.ui.BaseViewHolder import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.ui.HeaderViewHolder import org.oxycblt.auxio.ui.HeaderViewHolder
@ -46,8 +45,7 @@ import org.oxycblt.auxio.util.logE
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class QueueAdapter( class QueueAdapter(
private val touchHelper: ItemTouchHelper, private val touchHelper: ItemTouchHelper
private val playbackModel: PlaybackViewModel
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var data = mutableListOf<BaseModel>() private var data = mutableListOf<BaseModel>()
private var listDiffer = AsyncListDiffer(this, DiffCallback()) private var listDiffer = AsyncListDiffer(this, DiffCallback())
@ -55,14 +53,10 @@ class QueueAdapter(
override fun getItemCount(): Int = data.size override fun getItemCount(): Int = data.size
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when (val item = data[position]) { return when (data[position]) {
is Header -> if (item.isAction) {
USER_QUEUE_HEADER_ITEM_TYPE
} else {
HeaderViewHolder.ITEM_TYPE
}
is Song -> QUEUE_SONG_ITEM_TYPE is Song -> QUEUE_SONG_ITEM_TYPE
is Header -> HeaderViewHolder.ITEM_TYPE
is ActionHeader -> ActionHeaderViewHolder.ITEM_TYPE
else -> -1 else -> -1
} }
@ -70,29 +64,22 @@ class QueueAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) { return when (viewType) {
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
USER_QUEUE_HEADER_ITEM_TYPE -> UserQueueHeaderViewHolder(
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)
) )
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
ActionHeaderViewHolder.ITEM_TYPE -> ActionHeaderViewHolder.from(parent.context)
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 = data[position]) { when (val item = data[position]) {
is Header -> if (item.isAction) {
(holder as UserQueueHeaderViewHolder).bind(item)
} else {
(holder as HeaderViewHolder).bind(item)
}
is Song -> (holder as QueueSongViewHolder).bind(item) is Song -> (holder as QueueSongViewHolder).bind(item)
is Header -> (holder as HeaderViewHolder).bind(item)
is ActionHeader -> (holder as ActionHeaderViewHolder).bind(item)
else -> logE("Bad data given to QueueAdapter.") else -> logE("Bad data given to QueueAdapter.")
} }
@ -181,29 +168,6 @@ class QueueAdapter(
} }
} }
/*
* The viewholder for
*/
inner class UserQueueHeaderViewHolder(
private val binding: ItemActionHeaderBinding
) : BaseViewHolder<Header>(binding) {
override fun onBind(data: Header) {
binding.header = data
binding.headerButton.apply {
setImageResource(R.drawable.ic_clear)
contentDescription = context.getString(R.string.desc_clear_user_queue)
TooltipCompat.setTooltipText(this, contentDescription)
setOnClickListener {
playbackModel.clearUserQueue()
}
}
}
}
companion object { companion object {
const val QUEUE_SONG_ITEM_TYPE = 0xA005 const val QUEUE_SONG_ITEM_TYPE = 0xA005
const val USER_QUEUE_HEADER_ITEM_TYPE = 0xA006 const val USER_QUEUE_HEADER_ITEM_TYPE = 0xA006

View file

@ -90,6 +90,7 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
// an elevation change. // an elevation change.
// TODO: Maybe restrict the item from being drawn over the recycler bounds? // TODO: Maybe restrict the item from being drawn over the recycler bounds?
// Seems like its possible with enough UI magic // Seems like its possible with enough UI magic
// TODO: Add an accented BG to the removal action
val view = viewHolder.itemView val view = viewHolder.itemView

View file

@ -30,6 +30,7 @@ import androidx.recyclerview.widget.ItemTouchHelper
import kotlinx.coroutines.NonDisposableHandle.parent import kotlinx.coroutines.NonDisposableHandle.parent
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
@ -53,7 +54,7 @@ class QueueFragment : Fragment() {
val callback = QueueDragCallback(playbackModel) val callback = QueueDragCallback(playbackModel)
val helper = ItemTouchHelper(callback) val helper = ItemTouchHelper(callback)
val queueAdapter = QueueAdapter(helper, playbackModel) val queueAdapter = QueueAdapter(helper)
var lastShuffle = playbackModel.isShuffling.value var lastShuffle = playbackModel.isShuffling.value
callback.addQueueAdapter(queueAdapter) callback.addQueueAdapter(queueAdapter)
@ -120,10 +121,12 @@ class QueueFragment : Fragment() {
val nextQueue = playbackModel.nextItemsInQueue.value!! val nextQueue = playbackModel.nextItemsInQueue.value!!
if (userQueue.isNotEmpty()) { if (userQueue.isNotEmpty()) {
queue += Header( queue += ActionHeader(
id = -2, id = -2,
name = getString(R.string.lbl_next_user_queue), name = getString(R.string.lbl_next_user_queue),
isAction = true icon = R.drawable.ic_clear,
desc = R.string.desc_clear_user_queue,
onClick = { playbackModel.clearUserQueue() }
) )
queue += userQueue queue += userQueue
@ -132,8 +135,10 @@ class QueueFragment : Fragment() {
if (nextQueue.isNotEmpty()) { if (nextQueue.isNotEmpty()) {
queue += Header( queue += Header(
id = -3, id = -3,
name = getString(R.string.fmt_next_from, getParentName()), name = getString(
isAction = false R.string.fmt_next_from,
playbackModel.parent.value?.displayName ?: getString(R.string.lbl_all_songs)
)
) )
queue += nextQueue queue += nextQueue
@ -141,8 +146,4 @@ class QueueFragment : Fragment() {
return queue return queue
} }
private fun getParentName(): String {
return playbackModel.parent.value?.displayName ?: getString(R.string.lbl_all_songs)
}
} }

View file

@ -30,7 +30,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.appbar.AppBarLayout
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSearchBinding import org.oxycblt.auxio.databinding.FragmentSearchBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel
@ -74,9 +73,6 @@ class SearchFragment : Fragment() {
}, },
::newMenu ::newMenu
) )
val toolbarParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
// --- UI SETUP -- // --- UI SETUP --
binding.lifecycleOwner = viewLifecycleOwner binding.lifecycleOwner = viewLifecycleOwner

View file

@ -130,7 +130,11 @@ class SearchViewModel : ViewModel() {
*/ */
private fun List<BaseModel>.filterByOrNull(value: String): List<BaseModel>? { private fun List<BaseModel>.filterByOrNull(value: String): List<BaseModel>? {
val filtered = filter { val filtered = filter {
it.name.normalized().contains(value.normalized(), ignoreCase = true) // First see if the normal item name will work. If that fails, try the "normalized"
// [e.g all accented/unicode chars become latin chars] instead. Hopefully this
// shouldn't break other language's search functionality.
it.name.contains(value, ignoreCase = true) ||
it.name.normalized().contains(value, ignoreCase = true)
} }
return if (filtered.isNotEmpty()) filtered else null return if (filtered.isNotEmpty()) filtered else null
@ -155,8 +159,8 @@ class SearchViewModel : ViewModel() {
idx += Character.charCount(cp) idx += Character.charCount(cp)
when (Character.getType(cp)) { when (Character.getType(cp)) {
Character.NON_SPACING_MARK.toInt(), Character.COMBINING_SPACING_MARK.toInt() -> // Character.NON_SPACING_MARK and Character.COMBINING_SPACING_MARK
continue 6, 8 -> continue
else -> sb.appendCodePoint(cp) else -> sb.appendCodePoint(cp)
} }

View file

@ -18,14 +18,8 @@
package org.oxycblt.auxio.ui package org.oxycblt.auxio.ui
import android.widget.ImageButton
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.databinding.BindingAdapter
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
/** /**
@ -35,83 +29,11 @@ import org.oxycblt.auxio.music.Song
*/ */
enum class SortMode(@DrawableRes val iconRes: Int) { enum class SortMode(@DrawableRes val iconRes: Int) {
// Icons for each mode are assigned to the enums themselves // Icons for each mode are assigned to the enums themselves
NONE(R.drawable.ic_sort_none), NONE(R.drawable.ic_sort),
ALPHA_UP(R.drawable.ic_sort_alpha_up), ALPHA_UP(R.drawable.ic_sort),
ALPHA_DOWN(R.drawable.ic_sort_alpha_down), ALPHA_DOWN(R.drawable.ic_sort),
NUMERIC_UP(R.drawable.ic_sort_numeric_up), NUMERIC_UP(R.drawable.ic_sort),
NUMERIC_DOWN(R.drawable.ic_sort_numeric_down); NUMERIC_DOWN(R.drawable.ic_sort);
/**
* Get a sorted list of genres for a SortMode. Only supports alphabetic sorting.
* @param genres An unsorted list of genres.
* @return The sorted list of genres.
*/
fun getSortedGenreList(genres: List<Genre>): List<Genre> {
return when (this) {
ALPHA_UP -> genres.sortedWith(
compareByDescending(String.CASE_INSENSITIVE_ORDER) {
it.resolvedName.sliceArticle()
}
)
ALPHA_DOWN -> genres.sortedWith(
compareBy(String.CASE_INSENSITIVE_ORDER) {
it.resolvedName.sliceArticle()
}
)
else -> genres
}
}
/**
* Get a sorted list of artists for a SortMode. Only supports alphabetic sorting.
* @param artists An unsorted list of artists.
* @return The sorted list of artists.
*/
fun getSortedArtistList(artists: List<Artist>): List<Artist> {
return when (this) {
ALPHA_UP -> artists.sortedWith(
compareByDescending(String.CASE_INSENSITIVE_ORDER) {
it.name.sliceArticle()
}
)
ALPHA_DOWN -> artists.sortedWith(
compareBy(String.CASE_INSENSITIVE_ORDER) {
it.name.sliceArticle()
}
)
else -> artists
}
}
/**
* Get a sorted list of albums for a SortMode. Supports alpha + numeric sorting.
* @param albums An unsorted list of albums.
* @return The sorted list of albums.
*/
fun getSortedAlbumList(albums: List<Album>): List<Album> {
return when (this) {
ALPHA_UP -> albums.sortedWith(
compareByDescending(String.CASE_INSENSITIVE_ORDER) {
it.name.sliceArticle()
}
)
ALPHA_DOWN -> albums.sortedWith(
compareBy(String.CASE_INSENSITIVE_ORDER) {
it.name.sliceArticle()
}
)
NUMERIC_UP -> albums.sortedBy { it.year }
NUMERIC_DOWN -> albums.sortedByDescending { it.year }
else -> albums
}
}
/** /**
* Get a sorted list of songs for a SortMode. Supports alpha + numeric sorting. * Get a sorted list of songs for a SortMode. Supports alpha + numeric sorting.
@ -182,19 +104,6 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
} }
} }
/**
* Get a sorting menu ID for this mode. Alphabetic only.
* @return The action id for this mode.
*/
@IdRes
fun toMenuId(): Int {
return when (this) {
ALPHA_UP -> R.id.option_sort_asc
ALPHA_DOWN -> R.id.option_sort_dsc
else -> R.id.option_sort_dsc
}
}
/** /**
* Get the constant for this mode. Used to write a compressed variant to SettingsManager * Get the constant for this mode. Used to write a compressed variant to SettingsManager
* @return The int constant for this mode. * @return The int constant for this mode.
@ -234,14 +143,6 @@ enum class SortMode(@DrawableRes val iconRes: Int) {
} }
} }
/**
* Bind the [SortMode] icon for an ImageButton.
*/
@BindingAdapter("sortIcon")
fun ImageButton.bindSortIcon(mode: SortMode) {
setImageResource(mode.iconRes)
}
/** /**
* Slice a string so that any preceding articles like The/A(n) are truncated. * Slice a string so that any preceding articles like The/A(n) are truncated.
* This is hilariously anglo-centric, but its mostly for MediaStore compat and hopefully * This is hilariously anglo-centric, but its mostly for MediaStore compat and hopefully

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2021 Auxio Project * Copyright (c) 2021 Auxio Project
* ViewHolders.kt is part of Auxio. * SortHeaderViewHolder.kt is part of Auxio.
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -20,13 +20,16 @@ package org.oxycblt.auxio.ui
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import androidx.appcompat.widget.TooltipCompat
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemActionHeaderBinding
import org.oxycblt.auxio.databinding.ItemAlbumBinding import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.databinding.ItemHeaderBinding import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemSongBinding import org.oxycblt.auxio.databinding.ItemSongBinding
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
@ -245,3 +248,29 @@ class HeaderViewHolder private constructor(
} }
} }
} }
class ActionHeaderViewHolder private constructor(
private val binding: ItemActionHeaderBinding
) : BaseViewHolder<ActionHeader>(binding) {
override fun onBind(data: ActionHeader) {
binding.header = data
binding.headerButton.apply {
TooltipCompat.setTooltipText(this, contentDescription)
setOnClickListener(data.onClick)
}
}
companion object {
const val ITEM_TYPE = 0xA999 // TODO: Give this an ID
/**
* Create an instance of [ActionHeaderViewHolder]
*/
fun from(context: Context): ActionHeaderViewHolder {
return ActionHeaderViewHolder(ItemActionHeaderBinding.inflate(context.inflater))
}
}
}

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM21.41 6.34l-3.75-3.75-2.53 2.54 3.75 3.75 2.53-2.54z" />
</vector>

View file

@ -2,7 +2,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="?attr/colorAccent" android:tint="?attr/colorControlNormal"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path

View file

@ -1,13 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorAccent"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M3.694 17.992H0.976l4.142-12h3.27l4.136 12H9.806L6.8 8.734H6.706zm-0.17-4.717h6.422v1.98H3.524zm10.313 4.717v-1.506l5.988-8.402h-6V5.992h9.188v1.505L17.018 15.9h6.006v2.092z" />
<path
android:fillColor="@android:color/white"
android:pathData="M15.5 20.535h-7l3.5 3.456 3.5-3.456z" />
</vector>

View file

@ -1,13 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorAccent"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5 3.448h-7L12-0.008l3.5 3.456z" />
<path
android:fillColor="@android:color/white"
android:pathData="M3.694 17.992H0.976l4.142-12h3.27l4.136 12H9.806L6.8 8.734H6.706zm-0.17-4.717h6.422v1.98H3.524zm10.313 4.717v-1.506l5.988-8.402h-6V5.992h9.188v1.505L17.018 15.9h6.006v2.092z" />
</vector>

View file

@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="24dp"
android:height="24dp"
android:tint="?android:attr/colorAccent"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M6.542 18q-1.508-0.006-2.595-0.716-1.082-0.711-1.667-2.06-0.578-1.348-0.573-3.244 0-1.89 0.58-3.221 0.584-1.331 1.665-2.025 1.088-0.7 2.59-0.7 1.503 0 2.584 0.7 1.088 0.7 1.673 2.03 0.584 1.326 0.578 3.216 0 1.902-0.584 3.25-0.579 1.348-1.66 2.06Q8.05 18 6.542 18zm0-2.025q1.03 0 1.643-0.999 0.614-0.998 0.608-2.996 0-1.314-0.28-2.188-0.275-0.875-0.784-1.315-0.503-0.44-1.187-0.44-1.023 0-1.637 0.987-0.614 0.988-0.62 2.957 0 1.33 0.276 2.222 0.28 0.886 0.789 1.332 0.508 0.44 1.193 0.44zM17.51 6q0.924 0 1.777 0.3 0.86 0.293 1.532 0.953 0.679 0.66 1.07 1.749 0.398 1.083 0.404 2.668-0.006 1.472-0.351 2.629-0.34 1.15-0.976 1.958-0.638 0.806-1.538 1.23-0.895 0.417-2.005 0.417-1.199 0-2.117-0.446-0.918-0.45-1.479-1.224-0.555-0.779-0.672-1.749h2.496q0.146 0.632 0.614 0.982 0.468 0.344 1.158 0.344 1.169 0 1.777-0.982 0.608-0.987 0.614-2.702h-0.082Q19.463 12.635 19.007 13q-0.456 0.362-1.046 0.559-0.585 0.197-1.246 0.197-1.058 0-1.894-0.48-0.83-0.479-1.31-1.32-0.479-0.846-0.473-1.929-0.006-1.173 0.555-2.082 0.562-0.914 1.567-1.433Q16.172 5.994 17.51 6zm0.018 1.918q-0.59 0-1.053 0.271Q16.02 8.46 15.75 8.923q-0.263 0.462-0.257 1.038-0.006 0.575 0.252 1.032 0.263 0.457 0.719 0.728 0.456 0.265 1.04 0.265 0.439 0 0.807-0.158 0.374-0.158 0.65-0.434 0.28-0.282 0.438-0.65 0.158-0.372 0.163-0.795-0.005-0.558-0.269-1.02-0.263-0.463-0.725-0.734-0.461-0.277-1.04-0.277z"
tools:ignore="VectorPath" />
<path
android:fillColor="@android:color/white"
android:pathData="M15.5 20.544h-7L12 24l3.5-3.456z" />
</vector>

View file

@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorAccent"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M6.542 18q-1.508-0.006-2.595-0.716-1.082-0.711-1.667-2.06-0.578-1.348-0.573-3.244 0-1.89 0.58-3.221 0.584-1.331 1.665-2.025 1.088-0.7 2.59-0.7 1.503 0 2.584 0.7 1.088 0.7 1.673 2.03 0.584 1.326 0.578 3.216 0 1.902-0.584 3.25-0.579 1.348-1.66 2.06Q8.05 18 6.542 18zm0-2.025q1.03 0 1.643-0.999 0.614-0.998 0.608-2.996 0-1.314-0.28-2.188-0.275-0.875-0.784-1.315-0.503-0.44-1.187-0.44-1.023 0-1.637 0.987-0.614 0.988-0.62 2.957 0 1.33 0.276 2.222 0.28 0.886 0.789 1.332 0.508 0.44 1.193 0.44zM17.51 6q0.924 0 1.777 0.3 0.86 0.293 1.532 0.953 0.679 0.66 1.07 1.749 0.398 1.083 0.404 2.668-0.006 1.472-0.351 2.629-0.34 1.15-0.976 1.958-0.638 0.806-1.538 1.23-0.895 0.417-2.005 0.417-1.199 0-2.117-0.446-0.918-0.45-1.479-1.224-0.555-0.779-0.672-1.749h2.496q0.146 0.632 0.614 0.982 0.468 0.344 1.158 0.344 1.169 0 1.777-0.982 0.608-0.987 0.614-2.702h-0.082Q19.463 12.635 19.007 13q-0.456 0.362-1.046 0.559-0.585 0.197-1.246 0.197-1.058 0-1.894-0.48-0.83-0.479-1.31-1.32-0.479-0.846-0.473-1.929-0.006-1.173 0.555-2.082 0.562-0.914 1.567-1.433Q16.172 5.994 17.51 6zm0.018 1.918q-0.59 0-1.053 0.271Q16.02 8.46 15.75 8.923q-0.263 0.462-0.257 1.038-0.006 0.575 0.252 1.032 0.263 0.457 0.719 0.728 0.456 0.265 1.04 0.265 0.439 0 0.807-0.158 0.374-0.158 0.65-0.434 0.28-0.282 0.438-0.65 0.158-0.372 0.163-0.795-0.005-0.558-0.269-1.02-0.263-0.463-0.725-0.734-0.461-0.277-1.04-0.277z"
tools:ignore="VectorPath" />
<path
android:fillColor="@android:color/white"
android:pathData="M15.5 3.456h-7L12 0l3.5 3.456z" />
</vector>

View file

@ -7,7 +7,7 @@
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<stroke <stroke
android:width="@dimen/size_stroke_small" android:width="@dimen/size_stroke_small"
android:color="@color/overlay_divider" /> android:color="@color/mtrl_btn_stroke_color_selector" />
</shape> </shape>
</item> </item>
</layer-list> </layer-list>

View file

@ -1,137 +0,0 @@
<?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">
<data>
<variable
name="album"
type="org.oxycblt.auxio.music.Album" />
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<ImageView
android:id="@+id/album_cover"
style="@style/Widget.ImageView.Full"
android:layout_width="@dimen/size_cover_huge_land"
android:layout_height="@dimen/size_cover_huge_land"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_medium"
android:contentDescription="@{@string/desc_album_cover(album.name)}"
app:albumArt="@{album}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_album" />
<TextView
android:id="@+id/album_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{album.name}"
app:layout_constraintBottom_toTopOf="@+id/album_artist"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toTopOf="@+id/album_cover"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Album Name" />
<TextView
android:id="@+id/album_artist"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:clickable="true"
android:focusable="true"
android:onClick="@{() -> detailModel.navToItem(album.artist)}"
android:text="@{album.artist.name}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toTopOf="@+id/album_details"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toBottomOf="@+id/album_name"
tools:text="Artist Name" />
<TextView
android:id="@+id/album_details"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:albumDetails="@{album}"
app:layout_constraintBottom_toBottomOf="@+id/album_cover"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toBottomOf="@+id/album_artist"
tools:text="2020 / 10 Songs / 16:16" />
<com.google.android.material.button.MaterialButton
android:id="@+id/album_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:onClick="@{() -> playbackModel.playAlbum(album, false)}"
android:text="@string/lbl_play"
app:layout_constraintEnd_toStartOf="@+id/album_shuffle_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_cover" />
<com.google.android.material.button.MaterialButton
android:id="@+id/album_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:layout_marginStart="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_medium"
android:backgroundTint="?attr/colorAccent"
android:onClick="@{() -> playbackModel.playAlbum(album, true)}"
android:text="@string/lbl_shuffle"
app:layout_constraintBottom_toBottomOf="@+id/album_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_play_button"
app:layout_constraintTop_toTopOf="@+id/album_play_button" />
<TextView
android:id="@+id/album_song_header"
style="@style/Widget.TextView.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_small"
android:background="@drawable/ui_header_dividers"
android:text="@string/lbl_songs"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_play_button" />
<ImageButton
android:id="@+id/album_sort_button"
style="@style/Widget.Button.Unbounded.Small"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:contentDescription="@string/desc_sort_button"
android:onClick="@{() -> detailModel.incrementAlbumSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/album_song_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/album_song_header"
app:sortIcon="@{detailModel.albumSortMode}"
tools:src="@drawable/ic_sort_numeric_down" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,114 +0,0 @@
<?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=".detail.adapters.ArtistDetailAdapter.ArtistHeaderViewHolder">
<data>
<variable
name="artist"
type="org.oxycblt.auxio.music.Artist" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/artist_image"
android:layout_width="@dimen/size_cover_huge_land"
android:layout_height="@dimen/size_cover_huge_land"
android:layout_margin="@dimen/spacing_medium"
android:contentDescription="@{@string/desc_artist_image(artist.name)}"
style="@style/Widget.ImageView.Full"
app:artistImage="@{artist}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_artist" />
<TextView
android:id="@+id/artist_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{artist.name}"
app:layout_constraintBottom_toTopOf="@+id/artist_genre"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/artist_image"
app:layout_constraintTop_toTopOf="@+id/artist_image"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Artist Name" />
<TextView
android:id="@+id/artist_genre"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:artistGenre="@{artist}"
app:layout_constraintBottom_toTopOf="@+id/artist_counts"
app:layout_constraintStart_toEndOf="@+id/artist_image"
app:layout_constraintTop_toBottomOf="@+id/artist_name"
tools:text="Genre Name" />
<TextView
android:id="@+id/artist_counts"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:artistCounts="@{artist}"
app:layout_constraintBottom_toBottomOf="@+id/artist_image"
app:layout_constraintStart_toEndOf="@+id/artist_image"
app:layout_constraintTop_toBottomOf="@+id/artist_genre"
tools:text="2 Albums, 20 Songs" />
<com.google.android.material.button.MaterialButton
android:id="@+id/artist_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:onClick="@{() -> playbackModel.playArtist(artist, false)}"
android:text="@string/lbl_play"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:layout_marginStart="@dimen/spacing_medium"
app:layout_constraintEnd_toStartOf="@+id/artist_shuffle_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_image" />
<com.google.android.material.button.MaterialButton
android:id="@+id/artist_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:backgroundTint="?attr/colorAccent"
android:onClick="@{() -> playbackModel.playArtist(artist, true)}"
android:text="@string/lbl_shuffle"
android:layout_marginStart="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toBottomOf="@+id/artist_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/artist_play_button"
app:layout_constraintTop_toTopOf="@+id/artist_play_button" />
<TextView
android:id="@+id/artist_album_header"
android:layout_height="wrap_content"
android:layout_width="match_parent"
style="@style/Widget.TextView.Header"
android:layout_marginTop="@dimen/spacing_small"
android:text="@string/lbl_albums"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_play_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,87 @@
<?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">
<!-- This layout is re-used across all detail fragments. Do not add databinding. -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/spacing_medium">
<ImageView
android:id="@+id/detail_cover"
style="@style/Widget.ImageView.Full"
android:layout_width="@dimen/size_cover_huge_land"
android:layout_height="@dimen/size_cover_huge_land"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_artist"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/detail_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toTopOf="@+id/detail_subhead"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/detail_cover"
app:layout_constraintTop_toTopOf="@+id/detail_cover"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Name" />
<TextView
android:id="@+id/detail_subhead"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:clickable="true"
android:focusable="true"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toTopOf="@+id/detail_info"
app:layout_constraintStart_toEndOf="@+id/detail_cover"
app:layout_constraintTop_toBottomOf="@+id/detail_name"
tools:text="Info A" />
<TextView
android:id="@+id/detail_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="@+id/detail_cover"
app:layout_constraintStart_toEndOf="@+id/detail_cover"
app:layout_constraintTop_toBottomOf="@+id/detail_subhead"
tools:text="Info B" />
<com.google.android.material.button.MaterialButton
android:id="@+id/detail_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:text="@string/lbl_play"
app:layout_constraintEnd_toStartOf="@+id/detail_shuffle_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_cover" />
<com.google.android.material.button.MaterialButton
android:id="@+id/detail_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:layout_marginStart="@dimen/spacing_small"
android:backgroundTint="?attr/colorAccent"
android:text="@string/lbl_shuffle"
app:layout_constraintBottom_toBottomOf="@+id/detail_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/detail_play_button"
app:layout_constraintTop_toTopOf="@+id/detail_play_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,131 +0,0 @@
<?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=".detail.adapters.GenreDetailAdapter.GenreHeaderViewHolder">
<data>
<variable
name="genre"
type="org.oxycblt.auxio.music.Genre" />
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/genre_image"
android:layout_width="@dimen/size_cover_huge_land"
android:layout_height="@dimen/size_cover_huge_land"
android:layout_margin="@dimen/spacing_medium"
android:contentDescription="@{@string/desc_genre_image(genre.name)}"
style="@style/Widget.ImageView.Full"
app:genreImage="@{genre}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_genre" />
<TextView
android:id="@+id/genre_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{genre.resolvedName}"
app:layout_constraintBottom_toTopOf="@+id/genre_song_count"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/genre_image"
app:layout_constraintTop_toTopOf="@+id/genre_image"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Genre Name" />
<TextView
android:id="@+id/genre_song_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:text="@{@plurals/fmt_song_count(genre.songs.size(), genre.songs.size())}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toTopOf="@+id/genre_duration"
app:layout_constraintStart_toEndOf="@+id/genre_image"
app:layout_constraintTop_toBottomOf="@+id/genre_name"
tools:text="2 Artists, 4 Albums" />
<com.google.android.material.button.MaterialButton
android:id="@+id/genre_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:onClick="@{() -> playbackModel.playGenre(genre, false)}"
android:text="@string/lbl_play"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:layout_marginStart="@dimen/spacing_medium"
app:layout_constraintEnd_toStartOf="@+id/genre_shuffle_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/genre_image" />
<com.google.android.material.button.MaterialButton
android:id="@+id/genre_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:backgroundTint="?attr/colorAccent"
android:onClick="@{() -> playbackModel.playGenre(genre, true)}"
android:text="@string/lbl_shuffle"
android:layout_marginStart="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toBottomOf="@+id/genre_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/genre_play_button"
app:layout_constraintTop_toTopOf="@+id/genre_play_button" />
<TextView
android:id="@+id/genre_duration"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:text="@{genre.totalDuration}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="@+id/genre_image"
app:layout_constraintStart_toEndOf="@+id/genre_image"
app:layout_constraintTop_toBottomOf="@+id/genre_song_count"
tools:text="16:16" />
<TextView
android:id="@+id/genre_song_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.TextView.Header"
android:layout_marginTop="@dimen/spacing_small"
android:text="@string/lbl_songs"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/genre_play_button" />
<ImageButton
android:id="@+id/genre_sort_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/Widget.Button.Unbounded.Small"
android:contentDescription="@string/desc_sort_button"
android:onClick="@{() -> detailModel.incrementGenreSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/genre_song_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/genre_song_header"
app:sortIcon="@{detailModel.genreSortMode}"
tools:src="@drawable/ic_sort_alpha_down" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,136 +0,0 @@
<?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">
<data>
<variable
name="album"
type="org.oxycblt.auxio.music.Album" />
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<ImageView
android:id="@+id/album_cover"
android:layout_width="@dimen/size_cover_huge"
android:layout_height="@dimen/size_cover_huge"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_medium"
android:contentDescription="@{@string/desc_album_cover(album.name)}"
style="@style/Widget.ImageView.Full"
app:albumArt="@{album}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_album" />
<TextView
android:id="@+id/album_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{album.name}"
app:layout_constraintBottom_toTopOf="@+id/album_artist"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toTopOf="@+id/album_cover"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Album Name" />
<TextView
android:id="@+id/album_artist"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:clickable="true"
android:focusable="true"
android:onClick="@{() -> detailModel.navToItem(album.artist)}"
android:text="@{album.artist.name}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toTopOf="@+id/album_details"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toBottomOf="@+id/album_name"
tools:text="Artist Name" />
<TextView
android:id="@+id/album_details"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:albumDetails="@{album}"
app:layout_constraintBottom_toBottomOf="@+id/album_cover"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toBottomOf="@+id/album_artist"
tools:text="2020 / 10 Songs / 16:16" />
<com.google.android.material.button.MaterialButton
android:id="@+id/album_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:onClick="@{() -> playbackModel.playAlbum(album, false)}"
android:text="@string/lbl_play"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:layout_marginStart="@dimen/spacing_medium"
app:layout_constraintEnd_toStartOf="@+id/album_shuffle_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_cover" />
<com.google.android.material.button.MaterialButton
android:id="@+id/album_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:backgroundTint="?attr/colorAccent"
android:onClick="@{() -> playbackModel.playAlbum(album, true)}"
android:text="@string/lbl_shuffle"
android:layout_marginStart="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toBottomOf="@+id/album_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_play_button"
app:layout_constraintTop_toTopOf="@+id/album_play_button" />
<TextView
android:id="@+id/album_song_header"
style="@style/Widget.TextView.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_small"
android:background="@drawable/ui_header_dividers"
android:text="@string/lbl_songs"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_play_button" />
<ImageButton
android:id="@+id/album_sort_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/Widget.Button.Unbounded.Small"
android:contentDescription="@string/desc_sort_button"
android:onClick="@{() -> detailModel.incrementAlbumSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/album_song_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/album_song_header"
app:sortIcon="@{detailModel.albumSortMode}"
tools:src="@drawable/ic_sort_numeric_down" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,114 +0,0 @@
<?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=".detail.adapters.ArtistDetailAdapter.ArtistHeaderViewHolder">
<data>
<variable
name="artist"
type="org.oxycblt.auxio.music.Artist" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/artist_image"
android:layout_width="@dimen/size_cover_huge"
android:layout_height="@dimen/size_cover_huge"
android:layout_margin="@dimen/spacing_medium"
android:contentDescription="@{@string/desc_artist_image(artist.name)}"
style="@style/Widget.ImageView.Full"
app:artistImage="@{artist}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_artist" />
<TextView
android:id="@+id/artist_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{artist.name}"
app:layout_constraintBottom_toTopOf="@+id/artist_genre"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/artist_image"
app:layout_constraintTop_toTopOf="@+id/artist_image"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Artist Name" />
<TextView
android:id="@+id/artist_genre"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:artistGenre="@{artist}"
app:layout_constraintBottom_toTopOf="@+id/artist_counts"
app:layout_constraintStart_toEndOf="@+id/artist_image"
app:layout_constraintTop_toBottomOf="@+id/artist_name"
tools:text="Genre Name" />
<TextView
android:id="@+id/artist_counts"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:artistCounts="@{artist}"
app:layout_constraintBottom_toBottomOf="@+id/artist_image"
app:layout_constraintStart_toEndOf="@+id/artist_image"
app:layout_constraintTop_toBottomOf="@+id/artist_genre"
tools:text="2 Albums, 20 Songs" />
<com.google.android.material.button.MaterialButton
android:id="@+id/artist_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:onClick="@{() -> playbackModel.playArtist(artist, false)}"
android:text="@string/lbl_play"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:layout_marginStart="@dimen/spacing_medium"
app:layout_constraintEnd_toStartOf="@+id/artist_shuffle_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_image" />
<com.google.android.material.button.MaterialButton
android:id="@+id/artist_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:backgroundTint="?attr/colorAccent"
android:onClick="@{() -> playbackModel.playArtist(artist, true)}"
android:text="@string/lbl_shuffle"
android:layout_marginStart="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toBottomOf="@+id/artist_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/artist_play_button"
app:layout_constraintTop_toTopOf="@+id/artist_play_button" />
<TextView
android:id="@+id/artist_album_header"
android:layout_height="wrap_content"
android:layout_width="match_parent"
style="@style/Widget.TextView.Header"
android:layout_marginTop="@dimen/spacing_small"
android:text="@string/lbl_albums"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_play_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,84 @@
<?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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/spacing_medium">
<ImageView
android:id="@+id/detail_cover"
android:layout_width="@dimen/size_cover_huge"
android:layout_height="@dimen/size_cover_huge"
style="@style/Widget.ImageView.Full"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_artist"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/detail_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
app:layout_constraintBottom_toTopOf="@+id/detail_subhead"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/detail_cover"
app:layout_constraintTop_toTopOf="@+id/detail_cover"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Name" />
<TextView
android:id="@+id/detail_subhead"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:clickable="true"
android:focusable="true"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toTopOf="@+id/detail_info"
app:layout_constraintStart_toEndOf="@+id/detail_cover"
app:layout_constraintTop_toBottomOf="@+id/detail_name"
tools:text="Info A" />
<TextView
android:id="@+id/detail_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="@+id/detail_cover"
app:layout_constraintStart_toEndOf="@+id/detail_cover"
app:layout_constraintTop_toBottomOf="@+id/detail_subhead"
tools:text="Info B" />
<com.google.android.material.button.MaterialButton
android:id="@+id/detail_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:text="@string/lbl_play"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
app:layout_constraintEnd_toStartOf="@+id/detail_shuffle_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_cover" />
<com.google.android.material.button.MaterialButton
android:id="@+id/detail_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:backgroundTint="?attr/colorAccent"
android:text="@string/lbl_shuffle"
android:layout_marginStart="@dimen/spacing_small"
app:layout_constraintBottom_toBottomOf="@+id/detail_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/detail_play_button"
app:layout_constraintTop_toTopOf="@+id/detail_play_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,129 +0,0 @@
<?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=".detail.adapters.GenreDetailAdapter.GenreHeaderViewHolder">
<data>
<variable
name="genre"
type="org.oxycblt.auxio.music.Genre" />
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/genre_image"
android:layout_width="@dimen/size_cover_huge"
android:layout_height="@dimen/size_cover_huge"
android:layout_margin="@dimen/spacing_medium"
android:contentDescription="@{@string/desc_genre_image(genre.name)}"
style="@style/Widget.ImageView.Full"
app:genreImage="@{genre}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_genre" />
<TextView
android:id="@+id/genre_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{genre.resolvedName}"
app:layout_constraintBottom_toTopOf="@+id/genre_song_count"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/genre_image"
app:layout_constraintTop_toTopOf="@+id/genre_image"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Genre Name" />
<TextView
android:id="@+id/genre_song_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:text="@{@plurals/fmt_song_count(genre.songs.size(), genre.songs.size())}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toTopOf="@+id/genre_duration"
app:layout_constraintStart_toEndOf="@+id/genre_image"
app:layout_constraintTop_toBottomOf="@+id/genre_name"
tools:text="2 Artists, 4 Albums" />
<com.google.android.material.button.MaterialButton
android:id="@+id/genre_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:onClick="@{() -> playbackModel.playGenre(genre, false)}"
android:text="@string/lbl_play"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:layout_marginStart="@dimen/spacing_medium"
app:layout_constraintEnd_toStartOf="@+id/genre_shuffle_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/genre_image" />
<com.google.android.material.button.MaterialButton
android:id="@+id/genre_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:backgroundTint="?attr/colorAccent"
android:onClick="@{() -> playbackModel.playGenre(genre, true)}"
android:text="@string/lbl_shuffle"
android:layout_marginStart="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toBottomOf="@+id/genre_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/genre_play_button"
app:layout_constraintTop_toTopOf="@+id/genre_play_button" />
<TextView
android:id="@+id/genre_duration"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:text="@{genre.totalDuration}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="@+id/genre_image"
app:layout_constraintStart_toEndOf="@+id/genre_image"
app:layout_constraintTop_toBottomOf="@+id/genre_song_count"
tools:text="16:16" />
<TextView
android:id="@+id/genre_song_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.TextView.Header"
android:layout_marginTop="@dimen/spacing_small"
android:text="@string/lbl_songs"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/genre_play_button" />
<ImageButton
android:id="@+id/genre_sort_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/Widget.Button.Unbounded.Small"
android:contentDescription="@string/desc_sort_button"
android:onClick="@{() -> detailModel.incrementGenreSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/genre_song_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/genre_song_header"
app:sortIcon="@{detailModel.genreSortMode}"
tools:src="@drawable/ic_sort_alpha_down" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -29,6 +29,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:theme="@style/Theme.Neutral"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
@ -38,7 +39,7 @@
app:cardBackgroundColor="?attr/colorSurface" app:cardBackgroundColor="?attr/colorSurface"
app:cardCornerRadius="0dp" app:cardCornerRadius="0dp"
app:cardElevation="0dp" app:cardElevation="0dp"
app:strokeColor="@color/overlay_divider" app:strokeColor="@color/mtrl_btn_stroke_color_selector"
app:strokeWidth="1dp"> app:strokeWidth="1dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -158,7 +159,6 @@
<TextView <TextView
android:id="@+id/about_song_count" android:id="@+id/about_song_count"
style="@style/Widget.TextView.Icon" style="@style/Widget.TextView.Icon"
android:theme="@style/Theme.Neutral"
app:drawableStartCompat="@drawable/ic_song" app:drawableStartCompat="@drawable/ic_song"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -169,7 +169,7 @@
android:id="@+id/about_author" android:id="@+id/about_author"
style="@style/Widget.TextView.Icon" style="@style/Widget.TextView.Icon"
android:text="@string/lbl_author" android:text="@string/lbl_author"
app:drawableStartCompat="@drawable/ic_author" app:drawableStartCompat="@drawable/ic_artist"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/about_song_count" /> app:layout_constraintTop_toBottomOf="@+id/about_song_count" />

View file

@ -53,7 +53,7 @@
style="@style/Widget.TextView.Compact.Secondary" style="@style/Widget.TextView.Compact.Secondary"
android:layout_marginStart="@dimen/spacing_small" android:layout_marginStart="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small" android:layout_marginEnd="@dimen/spacing_small"
android:text="@{@string/format_info(song.album.artist.name, song.album.name)}" android:text="@{@string/fmt_two(song.album.artist.name, song.album.name)}"
app:layout_constraintBottom_toBottomOf="@+id/playback_cover" app:layout_constraintBottom_toBottomOf="@+id/playback_cover"
app:layout_constraintEnd_toStartOf="@+id/playback_play_pause" app:layout_constraintEnd_toStartOf="@+id/playback_play_pause"
app:layout_constraintStart_toEndOf="@+id/playback_cover" app:layout_constraintStart_toEndOf="@+id/playback_cover"

View file

@ -28,8 +28,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
tools:listitem="@layout/item_artist_header" tools:listitem="@layout/item_detail" />
tools:layout_marginTop="56dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View file

@ -4,12 +4,11 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.HeaderViewHolder"> tools:context=".ui.HeaderViewHolder">
<data> <data>
<variable <variable
name="header" name="header"
type="org.oxycblt.auxio.music.Header" /> type="org.oxycblt.auxio.music.ActionHeader" />
</data> </data>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -32,10 +31,12 @@
style="@style/Widget.Button.Unbounded.Small" style="@style/Widget.Button.Unbounded.Small"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@{context.getString(header.desc)}"
android:src="@{context.getDrawable(header.icon)}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" tools:src="@drawable/ic_sort"/>
tools:src="@drawable/ic_clear" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -37,7 +37,7 @@
<TextView <TextView
android:id="@+id/album_info" android:id="@+id/album_info"
style="@style/Widget.TextView.Item.Secondary" style="@style/Widget.TextView.Item.Secondary"
app:albumInfo="@{album}" android:text="@{@string/fmt_two(album.artist.name, @plurals/fmt_song_count(album.songs.size, album.songs.size))}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover" app:layout_constraintStart_toEndOf="@+id/album_cover"

View file

@ -1,128 +0,0 @@
<?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">
<data>
<variable
name="album"
type="org.oxycblt.auxio.music.Album" />
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<ImageView
android:id="@+id/album_cover"
style="@style/Widget.ImageView.Full"
android:layout_width="@dimen/size_cover_huge"
android:layout_height="@dimen/size_cover_huge"
android:layout_marginTop="@dimen/spacing_medium"
android:contentDescription="@{@string/desc_album_cover(album.name)}"
app:albumArt="@{album}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_album" />
<TextView
android:id="@+id/album_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{album.name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_cover"
tools:text="Album Name" />
<TextView
android:id="@+id/album_artist"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:onClick="@{() -> detailModel.navToItem(album.artist)}"
android:text="@{album.artist.name}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_name"
tools:text="Artist Name" />
<TextView
android:id="@+id/album_details"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:albumDetails="@{album}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_artist"
tools:text="2020 / 10 Songs / 16:16" />
<com.google.android.material.button.MaterialButton
android:id="@+id/album_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:onClick="@{() -> playbackModel.playAlbum(album, false)}"
android:text="@string/lbl_play"
app:layout_constraintEnd_toStartOf="@+id/album_shuffle_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_details" />
<com.google.android.material.button.MaterialButton
android:id="@+id/album_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:layout_marginStart="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_medium"
android:backgroundTint="?attr/colorAccent"
android:onClick="@{() -> playbackModel.playAlbum(album, true)}"
android:text="@string/lbl_shuffle"
app:layout_constraintBottom_toBottomOf="@+id/album_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_play_button"
app:layout_constraintTop_toTopOf="@+id/album_play_button" />
<TextView
android:id="@+id/album_song_header"
style="@style/Widget.TextView.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_small"
android:background="@drawable/ui_header_dividers"
android:text="@string/lbl_songs"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_play_button" />
<ImageButton
android:id="@+id/album_sort_button"
style="@style/Widget.Button.Unbounded.Small"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:contentDescription="@string/desc_sort_button"
android:onClick="@{() -> detailModel.incrementAlbumSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/album_song_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/album_song_header"
app:sortIcon="@{detailModel.albumSortMode}"
tools:src="@drawable/ic_sort_numeric_down" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".detail.adapters.AlbumDetailAdapter.AlbumSongViewHolder"> tools:context=".detail.recycler.AlbumDetailAdapter.AlbumSongViewHolder">
<data> <data>

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".detail.adapters.ArtistDetailAdapter.ArtistAlbumViewHolder"> tools:context=".detail.recycler.ArtistDetailAdapter.ArtistAlbumViewHolder">
<data> <data>
@ -38,7 +38,7 @@
<TextView <TextView
android:id="@+id/album_year" android:id="@+id/album_year"
style="@style/Widget.TextView.Item.Secondary" style="@style/Widget.TextView.Item.Secondary"
app:albumYear="@{album}" android:text="@{album.year != 0 ? String.valueOf(album.year) : @string/def_date}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover" app:layout_constraintStart_toEndOf="@+id/album_cover"

View file

@ -1,114 +0,0 @@
<?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=".detail.adapters.ArtistDetailAdapter.ArtistHeaderViewHolder">
<data>
<variable
name="artist"
type="org.oxycblt.auxio.music.Artist" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/artist_image"
style="@style/Widget.ImageView.Full"
android:layout_width="@dimen/size_cover_huge"
android:layout_height="@dimen/size_cover_huge"
android:layout_marginTop="@dimen/spacing_medium"
android:contentDescription="@{@string/desc_artist_image(artist.name)}"
app:artistImage="@{artist}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_artist" />
<TextView
android:id="@+id/artist_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{artist.name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_image"
tools:text="Artist Name" />
<TextView
android:id="@+id/artist_genre"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:artistGenre="@{artist}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_name"
tools:text="Genre Name" />
<TextView
android:id="@+id/artist_counts"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:artistCounts="@{artist}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_genre"
tools:text="2 Albums, 20 Songs" />
<com.google.android.material.button.MaterialButton
android:id="@+id/artist_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:onClick="@{() -> playbackModel.playArtist(artist, false)}"
android:text="@string/lbl_play"
app:layout_constraintEnd_toStartOf="@+id/artist_shuffle_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
<com.google.android.material.button.MaterialButton
android:id="@+id/artist_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:layout_marginStart="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_medium"
android:backgroundTint="?attr/colorAccent"
android:onClick="@{() -> playbackModel.playArtist(artist, true)}"
android:text="@string/lbl_shuffle"
app:layout_constraintBottom_toBottomOf="@+id/artist_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/artist_play_button"
app:layout_constraintTop_toTopOf="@+id/artist_play_button" />
<TextView
android:id="@+id/artist_song_header"
style="@style/Widget.TextView.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_small"
android:background="@drawable/ui_header_dividers"
android:text="@string/lbl_albums"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_play_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,78 @@
<?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">
<!-- This layout is re-used across all detail fragments. Do not add databinding. -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/spacing_medium">
<ImageView
android:id="@+id/detail_cover"
style="@style/Widget.ImageView.Full"
android:layout_width="@dimen/size_cover_huge"
android:layout_height="@dimen/size_cover_huge"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_artist"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/detail_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_cover"
tools:text="Name" />
<TextView
android:id="@+id/detail_subhead"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_name"
tools:text="Info A" />
<TextView
android:id="@+id/detail_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_subhead"
tools:text="Info B" />
<com.google.android.material.button.MaterialButton
android:id="@+id/detail_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:text="@string/lbl_play"
app:layout_constraintEnd_toStartOf="@+id/detail_shuffle_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detail_info" />
<com.google.android.material.button.MaterialButton
android:id="@+id/detail_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:layout_marginStart="@dimen/spacing_small"
android:backgroundTint="?attr/colorAccent"
android:text="@string/lbl_shuffle"
app:layout_constraintBottom_toBottomOf="@+id/detail_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/detail_play_button"
app:layout_constraintTop_toTopOf="@+id/detail_play_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -1,126 +0,0 @@
<?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=".detail.adapters.GenreDetailAdapter.GenreHeaderViewHolder">
<data>
<variable
name="genre"
type="org.oxycblt.auxio.music.Genre" />
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/genre_image"
style="@style/Widget.ImageView.Full"
android:layout_width="@dimen/size_cover_huge"
android:layout_height="@dimen/size_cover_huge"
android:layout_marginTop="@dimen/spacing_medium"
android:contentDescription="@{@string/desc_genre_image(genre.name)}"
app:genreImage="@{genre}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_genre" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/genre_name"
style="@style/Widget.TextView.Detail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_medium"
android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{genre.resolvedName}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/genre_image"
tools:text="Genre Name" />
<TextView
android:id="@+id/genre_song_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:text="@{@plurals/fmt_song_count(genre.songs.size(), genre.songs.size())}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/genre_name"
tools:text="80 Songs" />
<TextView
android:id="@+id/genre_duration"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_medium"
android:text="@{genre.totalDuration}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/genre_song_count"
tools:text="16:16" />
<com.google.android.material.button.MaterialButton
android:id="@+id/genre_play_button"
style="@style/Widget.Button.Vibrant.Secondary"
android:layout_marginStart="@dimen/spacing_medium"
android:layout_marginTop="@dimen/spacing_small"
android:layout_marginEnd="@dimen/spacing_small"
android:onClick="@{() -> playbackModel.playGenre(genre, false)}"
android:text="@string/lbl_play"
app:layout_constraintEnd_toStartOf="@+id/genre_shuffle_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/genre_duration" />
<com.google.android.material.button.MaterialButton
android:id="@+id/genre_shuffle_button"
style="@style/Widget.Button.Vibrant.Primary"
android:backgroundTint="?attr/colorAccent"
android:onClick="@{() -> playbackModel.playGenre(genre, true)}"
android:text="@string/lbl_shuffle"
android:layout_marginEnd="@dimen/spacing_medium"
app:layout_constraintBottom_toBottomOf="@+id/genre_play_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/genre_play_button"
app:layout_constraintTop_toTopOf="@+id/genre_play_button" />
<TextView
android:id="@+id/genre_song_header"
style="@style/Widget.TextView.Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_small"
android:text="@string/lbl_songs"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/genre_play_button" />
<ImageButton
android:id="@+id/genre_sort_button"
style="@style/Widget.Button.Unbounded.Small"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:contentDescription="@string/desc_sort_button"
android:onClick="@{() -> detailModel.incrementGenreSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/genre_song_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/genre_song_header"
app:sortIcon="@{detailModel.genreSortMode}"
tools:src="@drawable/ic_sort_alpha_down" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -2,7 +2,7 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".detail.adapters.GenreDetailAdapter.GenreSongViewHolder"> tools:context=".detail.recycler.GenreDetailAdapter.GenreSongViewHolder">
<data> <data>
@ -40,7 +40,7 @@
android:id="@+id/song_info" android:id="@+id/song_info"
style="@style/Widget.TextView.Item.Secondary" style="@style/Widget.TextView.Item.Secondary"
android:layout_marginEnd="@dimen/spacing_medium" android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{@string/format_info(song.album.artist.name, song.album.name)}" android:text="@{@string/fmt_two(song.album.artist.name, song.album.name)}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/song_duration" app:layout_constraintEnd_toStartOf="@+id/song_duration"
app:layout_constraintStart_toEndOf="@+id/album_cover" app:layout_constraintStart_toEndOf="@+id/album_cover"

View file

@ -43,7 +43,7 @@
android:id="@+id/song_info" android:id="@+id/song_info"
style="@style/Widget.TextView.Item.Secondary" style="@style/Widget.TextView.Item.Secondary"
android:layout_marginEnd="@dimen/spacing_medium" android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{@string/format_info(song.album.artist.name, song.album.name)}" android:text="@{@string/fmt_two(song.album.artist.name, song.album.name)}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/song_drag_handle" app:layout_constraintEnd_toStartOf="@+id/song_drag_handle"
app:layout_constraintStart_toEndOf="@+id/album_cover" app:layout_constraintStart_toEndOf="@+id/album_cover"
@ -52,13 +52,14 @@
<ImageView <ImageView
android:id="@+id/song_drag_handle" android:id="@+id/song_drag_handle"
android:layout_width="@dimen/size_btn_small" android:layout_width="wrap_content"
android:layout_height="@dimen/size_btn_small" android:layout_height="@dimen/size_btn_small"
android:layout_marginEnd="@dimen/spacing_small"
android:clickable="true" android:clickable="true"
android:contentDescription="@string/desc_queue_handle" android:contentDescription="@string/desc_queue_handle"
android:focusable="true" android:focusable="true"
android:scaleType="center" android:scaleType="center"
android:paddingStart="@dimen/spacing_medium"
android:paddingEnd="@dimen/spacing_medium"
android:src="@drawable/ic_handle" android:src="@drawable/ic_handle"
app:layout_constraintBottom_toBottomOf="@+id/album_cover" app:layout_constraintBottom_toBottomOf="@+id/album_cover"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -40,7 +40,7 @@
android:id="@+id/song_info" android:id="@+id/song_info"
style="@style/Widget.TextView.Item.Secondary" style="@style/Widget.TextView.Item.Secondary"
android:layout_marginEnd="@dimen/spacing_medium" android:layout_marginEnd="@dimen/spacing_medium"
android:text="@{@string/format_info(song.album.artist.name, song.album.name)}" android:text="@{@string/fmt_two(song.album.artist.name, song.album.name)}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover" app:layout_constraintStart_toEndOf="@+id/album_cover"

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="@+id/option_sort_asc"
android:title="@string/lbl_sort_asc" />
<item
android:id="@+id/option_sort_dsc"
android:title="@string/lbl_sort_dsc" />
<item
android:id="@+id/option_sort_artist"
android:title="@string/lbl_sort_artist" />
<item
android:id="@+id/option_sort_album"
android:title="@string/lbl_sort_album" />
<item
android:id="@+id/option_sort_year"
android:title="@string/lbl_sort_year" />
</group>
</menu>

View file

@ -10,7 +10,7 @@
<item <item
android:id="@+id/submenu_sorting" android:id="@+id/submenu_sorting"
android:icon="@drawable/ic_sort_none" android:icon="@drawable/ic_sort"
android:title="@string/lbl_sort" android:title="@string/lbl_sort"
app:showAsAction="ifRoom"> app:showAsAction="ifRoom">
<menu> <menu>

View file

@ -100,7 +100,7 @@
<string name="hint_search_library">"Prohledat vaši knihovnu…"</string> <string name="hint_search_library">"Prohledat vaši knihovnu…"</string>
<!-- Description Namespace | Accessibility Strings --> <!-- Description Namespace | Accessibility Strings -->
<string name="desc_sort_button">"Změnit pořadí řazení"</string> <string name="desc_sort">"Změnit pořadí řazení"</string>
<string name="desc_track_number">"Stopa %d"</string> <string name="desc_track_number">"Stopa %d"</string>
<string name="desc_play_pause">"Přehrát nebo pozastavit"</string> <string name="desc_play_pause">"Přehrát nebo pozastavit"</string>
<string name="desc_skip_next">"Přeskočit na další skladbu"</string> <string name="desc_skip_next">"Přeskočit na další skladbu"</string>

View file

@ -103,7 +103,7 @@
<string name="hint_search_library">Musikbibliothek durchsuchen…</string> <string name="hint_search_library">Musikbibliothek durchsuchen…</string>
<!-- Description Namespace | Accessibility Strings --> <!-- Description Namespace | Accessibility Strings -->
<string name="desc_sort_button">Reihenfolge ändern</string> <string name="desc_sort">Reihenfolge ändern</string>
<string name="desc_track_number">Titel %d</string> <string name="desc_track_number">Titel %d</string>
<string name="desc_play_pause">Abspielen oder Pausieren</string> <string name="desc_play_pause">Abspielen oder Pausieren</string>

View file

@ -106,7 +106,7 @@
<string name="hint_search_library">Busca en tu biblioteca…</string> <string name="hint_search_library">Busca en tu biblioteca…</string>
<!-- Description Namespace | Accessibility Strings --> <!-- Description Namespace | Accessibility Strings -->
<string name="desc_sort_button">Cambiar el orden de clasificación</string> <string name="desc_sort">Cambiar el orden de clasificación</string>
<string name="desc_track_number">Pista %d</string> <string name="desc_track_number">Pista %d</string>
<string name="desc_play_pause">Reproducir o Pausar</string> <string name="desc_play_pause">Reproducir o Pausar</string>

View file

@ -104,7 +104,7 @@
<string name="hint_search_library">Zoek in uw bibliotheek…</string> <string name="hint_search_library">Zoek in uw bibliotheek…</string>
<!-- Description Namespace | Accessibility Strings --> <!-- Description Namespace | Accessibility Strings -->
<string name="desc_sort_button">Sorteervolgorde wijzigen</string> <string name="desc_sort">Sorteervolgorde wijzigen</string>
<string name="desc_track_number">Nummer %d</string> <string name="desc_track_number">Nummer %d</string>
<string name="desc_play_pause">Afspelen/Pauzeren</string> <string name="desc_play_pause">Afspelen/Pauzeren</string>

View file

@ -14,8 +14,8 @@
<dimen name="size_cover_compact">48dp</dimen> <dimen name="size_cover_compact">48dp</dimen>
<dimen name="size_cover_normal">56dp</dimen> <dimen name="size_cover_normal">56dp</dimen>
<dimen name="size_cover_huge_land">136dp</dimen> <dimen name="size_cover_huge_land">128dp</dimen>
<dimen name="size_cover_huge">264dp</dimen> <dimen name="size_cover_huge">256dp</dimen>
<dimen name="size_stroke_small">1dp</dimen> <dimen name="size_stroke_small">1dp</dimen>
<dimen name="size_stroke_large">2dp</dimen> <dimen name="size_stroke_large">2dp</dimen>

View file

@ -4,8 +4,8 @@
<string name="info_app_name" translatable="false">Auxio</string> <string name="info_app_name" translatable="false">Auxio</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<string name="format_info" translatable="false">%1$s • %2$s</string> <string name="fmt_two" translatable="false">%1$s • %2$s</string>
<string name="format_double_info" translatable="false">%1$s • %2$s • %3$s</string> <string name="fmt_three" translatable="false">%1$s • %2$s • %3$s</string>
<string name="format_double_counts" translatable="false">%1$s, %2$s</string> <string name="fmt_counts" translatable="false">%1$s, %2$s</string>
<string name="format_accent_summary" translatable="false">&lt;b>%1$s&lt;/b>:&#160;%2$s</string> <string name="fmt_accent_desc" translatable="false">&lt;b>%1$s&lt;/b>:&#160;%2$s</string>
</resources> </resources>

View file

@ -114,7 +114,6 @@
<string name="hint_search_library">Search your library…</string> <string name="hint_search_library">Search your library…</string>
<!-- Description Namespace | Accessibility Strings --> <!-- Description Namespace | Accessibility Strings -->
<string name="desc_sort_button">Change Sort Order</string>
<string name="desc_track_number">Track %d</string> <string name="desc_track_number">Track %d</string>
<string name="desc_play_pause">Play or Pause</string> <string name="desc_play_pause">Play or Pause</string>

View file

@ -11,7 +11,7 @@
<style name="Theme.Base" parent="Theme.Splash"> <style name="Theme.Base" parent="Theme.Splash">
<!-- Colors --> <!-- Colors -->
<item name="colorSurface">@color/surface</item> <item name="colorSurface">@color/surface</item>
<item name="colorAccent">@color/design_default_color_primary</item> <item name="colorAccent">@color/blue</item>
<item name="colorPrimary">?attr/colorAccent</item> <item name="colorPrimary">?attr/colorAccent</item>
<item name="colorOnPrimary">?attr/colorSurface</item> <item name="colorOnPrimary">?attr/colorSurface</item>

View file

@ -197,26 +197,24 @@
<item name="android:background">@drawable/ui_small_unbounded_ripple</item> <item name="android:background">@drawable/ui_small_unbounded_ripple</item>
</style> </style>
<style name="Widget.Button.Vibrant.Base" parent="@style/Widget.MaterialComponents.Button.TextButton"> <style name="Widget.Button.Vibrant.Primary" parent="@style/Widget.MaterialComponents.Button.TextButton">
<item name="android:layout_width">0dp</item> <item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:clickable">true</item> <item name="android:letterSpacing">0.05</item>
<item name="android:focusable">true</item> <item name="android:textAllCaps">false</item>
<item name="fontFamily">@font/inter_semibold</item>
<item name="android:textSize">@dimen/text_size_small</item> <item name="android:textSize">@dimen/text_size_small</item>
<item name="textAllCaps">false</item> <item name="android:textColor">?attr/colorSurface</item>
<item name="fontFamily">@font/inter_semibold</item>
<item name="cornerRadius">0dp</item> <item name="cornerRadius">0dp</item>
</style> </style>
<style name="Widget.Button.Vibrant.Primary" parent="@style/Widget.Button.Vibrant.Base"> <style name="Widget.Button.Vibrant.Secondary" parent="@style/Widget.MaterialComponents.Button.OutlinedButton">
<item name="android:textColor">?attr/colorSurface</item> <item name="android:layout_width">0dp</item>
<item name="backgroundTint">?attr/colorAccent</item> <item name="android:layout_height">wrap_content</item>
<item name="rippleColor">@color/mtrl_btn_ripple_color</item> <item name="android:letterSpacing">0.05</item>
</style> <item name="android:textAllCaps">false</item>
<item name="android:textSize">@dimen/text_size_small</item>
<style name="Widget.Button.Vibrant.Secondary" parent="@style/Widget.Button.Vibrant.Base"> <item name="fontFamily">@font/inter_semibold</item>
<item name="strokeColor">@color/overlay_divider</item> <item name="cornerRadius">0dp</item>
<item name="strokeWidth">@dimen/size_stroke_small</item>
<item name="rippleColor">?attr/colorControlHighlight</item>
</style> </style>
</resources> </resources>