Refactor album detail layout

Change the album detail layout to rely on a RecyclerView entirely instead of a NestedScrollView, at the cost of some functionality I'll need to re-add.
This commit is contained in:
OxygenCobalt 2020-12-29 09:03:14 -07:00
parent 7ef4eb5fde
commit 46fa300252
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
26 changed files with 418 additions and 532 deletions

View file

@ -14,7 +14,7 @@
## About
Auxio is a local music player for android partially inspired by both Spotify and other FOSS music players such as [Music Player GO](https://github.com/enricocid/Music-Player-GO) and [Phonograph](https://github.com/kabouzeid/Phonograph), albeit with a heavy emphasis on a simple and sensible, however customizable UI/UX.
Auxio is a local music player for android partially inspired by both Spotify and other FOSS music players such as [Music Player GO](https://github.com/enricocid/Music-Player-GO) and [Phonograph](https://github.com/kabouzeid/Phonograph), albeit with a heavy emphasis on a simple and straightfoward, however customizable UI/UX.
Unlike other music players, Auxio is based off of [ExoPlayer](https://exoplayer.dev/), allowing for much better listening experience compared to the native [MediaPlayer](https://developer.android.com/guide/topics/media/mediaplayer) API. Auxio's codebase is also designed to be extendable, allowing for the addition of features that are not included in the main app.
@ -53,7 +53,7 @@ Unlike other music players, Auxio is based off of [ExoPlayer](https://exoplayer.
- Better music loading system
- Improved genre/artist/album UIs
- Dedicated search tab
- New search setup
- Swipe-to-next-track function
- Artist Images
- Black theme

View file

@ -97,7 +97,7 @@ dependencies {
implementation "com.google.android.exoplayer:extension-mediasession:$exoplayer_version"
// Image loading
implementation 'io.coil-kt:coil:0.13.0'
implementation 'io.coil-kt:coil:1.1.0'
// Material
implementation 'com.google.android.material:material:1.3.0-beta01'

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.oxycblt.auxio">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@ -10,17 +9,17 @@
<queries />
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:allowBackup="true"
android:theme="@style/Theme.Base"
android:fullBackupContent="@xml/backup_descriptor">
android:theme="@style/Theme.Base">
<activity
android:name=".MainActivity"
android:launchMode="singleInstance"
android:icon="@mipmap/ic_launcher"
android:launchMode="singleInstance"
android:roundIcon="@mipmap/ic_launcher_round"
android:windowSoftInputMode="adjustPan">
@ -32,12 +31,12 @@
</activity>
<service
android:name=".playback.PlaybackService"
android:description="@string/label_service_playback"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="mediaPlayback"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:foregroundServiceType="mediaPlayback"
android:exported="false"
android:enabled="true"
android:description="@string/label_service_playback"
android:stopWithTask="false" />
</application>
</manifest>

View file

@ -5,20 +5,19 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentAlbumDetailBinding
import org.oxycblt.auxio.detail.adapters.AlbumSongAdapter
import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.adapters.AlbumDetailAdapter
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.recycler.LinearCenterScroller
import org.oxycblt.auxio.ui.createToast
import org.oxycblt.auxio.ui.disable
import org.oxycblt.auxio.ui.setupAlbumSongActions
/**
@ -26,17 +25,13 @@ import org.oxycblt.auxio.ui.setupAlbumSongActions
* @author OxygenCobalt
*/
class AlbumDetailFragment : DetailFragment() {
private val args: AlbumDetailFragmentArgs by navArgs()
private val playbackModel: PlaybackViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentAlbumDetailBinding.inflate(inflater)
// If DetailViewModel isn't already storing the album, get it from MusicStore
// using the ID given by the navigation arguments.
if (detailModel.currentAlbum.value == null ||
@ -49,7 +44,8 @@ class AlbumDetailFragment : DetailFragment() {
)
}
val songAdapter = AlbumSongAdapter(
val detailAdapter = AlbumDetailAdapter(
detailModel, viewLifecycleOwner,
doOnClick = { playbackModel.playSong(it, PlaybackMode.IN_ALBUM) },
doOnLongClick = { data, view ->
PopupMenu(requireContext(), view).setupAlbumSongActions(
@ -58,22 +54,12 @@ class AlbumDetailFragment : DetailFragment() {
}
)
var lastHolder: AlbumSongAdapter.ViewHolder? = null
// --- UI SETUP ---
binding.lifecycleOwner = this
binding.detailModel = detailModel
binding.playbackModel = playbackModel
binding.album = detailModel.currentAlbum.value!!
binding.albumToolbar.apply {
setNavigationOnClickListener {
findNavController().navigateUp()
}
setOnMenuItemClickListener {
when (it.itemId) {
setupToolbar(R.menu.menu_album_actions) {
when (it) {
R.id.action_shuffle -> {
playbackModel.playAlbum(
detailModel.currentAlbum.value!!, true
@ -91,7 +77,7 @@ class AlbumDetailFragment : DetailFragment() {
R.id.action_queue_add -> {
playbackModel.addToUserQueue(detailModel.currentAlbum.value!!)
context.getString(R.string.label_queue_added).createToast(requireContext())
getString(R.string.label_queue_added).createToast(requireContext())
true
}
@ -99,18 +85,12 @@ class AlbumDetailFragment : DetailFragment() {
else -> false
}
}
}
binding.albumSongRecycler.apply {
adapter = songAdapter
binding.detailRecycler.apply {
adapter = detailAdapter
setHasFixedSize(true)
}
// Don't enable the sort button if there's only one song [or less]
if (detailModel.currentAlbum.value!!.songs.size < 2) {
binding.albumSortButton.disable()
}
// If this fragment was created in order to nav to an item, then snap scroll to that item.
playbackModel.navToItem.value?.let {
if (it is Song) {
@ -123,13 +103,11 @@ class AlbumDetailFragment : DetailFragment() {
detailModel.albumSortMode.observe(viewLifecycleOwner) { mode ->
logD("Updating sort mode to $mode")
// Update the current sort icon
binding.albumSortButton.setImageResource(mode.iconRes)
val data = mutableListOf<BaseModel>(detailModel.currentAlbum.value!!).also {
it.addAll(mode.getSortedSongList(detailModel.currentAlbum.value!!.songs))
}
// Then update the sort mode of the album adapter.
songAdapter.submitList(
mode.getSortedSongList(detailModel.currentAlbum.value!!.songs)
)
detailAdapter.submitList(data)
}
detailModel.doneWithNavToParent()
@ -148,32 +126,6 @@ class AlbumDetailFragment : DetailFragment() {
}
}
playbackModel.song.observe(viewLifecycleOwner) { song ->
if (song != null) {
val pos = detailModel.albumSortMode.value!!.getSortedSongList(
detailModel.currentAlbum.value!!.songs
).indexOfFirst { it.id == song.id }
if (pos != -1) {
binding.albumSongRecycler.post {
lastHolder?.removePlaying()
lastHolder = binding.albumSongRecycler.getChildViewHolder(
binding.albumSongRecycler.getChildAt(pos)
) as AlbumSongAdapter.ViewHolder
lastHolder?.setPlaying(requireContext())
}
return@observe
} else {
lastHolder?.removePlaying()
}
}
lastHolder?.removePlaying()
}
playbackModel.navToItem.observe(viewLifecycleOwner) {
if (it != null) {
if (it is Song) {
@ -196,22 +148,18 @@ class AlbumDetailFragment : DetailFragment() {
* @param binding The binding required
* @param smooth Whether to scroll smoothly or not, true for yes, false for no.
*/
private fun scrollToPlayingItem(binding: FragmentAlbumDetailBinding, smooth: Boolean) {
private fun scrollToPlayingItem(binding: FragmentDetailBinding, smooth: Boolean) {
// Calculate where the item for the currently played song is, and scroll to there
val pos = detailModel.albumSortMode.value!!.getSortedSongList(
detailModel.currentAlbum.value!!.songs
).indexOf(playbackModel.song.value)
if (pos != -1) {
binding.albumSongRecycler.post {
val y = binding.albumSongRecycler.y +
binding.albumSongRecycler.getChildAt(pos).y
if (smooth) {
binding.nestedScroll.smoothScrollTo(0, y.toInt())
} else {
binding.nestedScroll.scrollTo(0, y.toInt())
}
// TODO: Re-add snap scrolling.
binding.detailRecycler.post {
binding.detailRecycler.layoutManager?.startSmoothScroll(
LinearCenterScroller(pos)
)
}
playbackModel.doneWithNavToItem()

View file

@ -5,17 +5,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.adapters.ArtistAlbumAdapter
import org.oxycblt.auxio.detail.adapters.ArtistDetailAdapter
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.setupAlbumActions
/**
@ -24,15 +21,12 @@ import org.oxycblt.auxio.ui.setupAlbumActions
*/
class ArtistDetailFragment : DetailFragment() {
private val args: ArtistDetailFragmentArgs by navArgs()
private val playbackModel: PlaybackViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentDetailBinding.inflate(inflater)
// If DetailViewModel isn't already storing the artist, get it from MusicStore
// using the ID given by the navigation arguments
if (detailModel.currentArtist.value == null ||
@ -45,7 +39,7 @@ class ArtistDetailFragment : DetailFragment() {
)
}
val albumAdapter = ArtistAlbumAdapter(
val detailAdapter = ArtistDetailAdapter(
detailModel, viewLifecycleOwner,
doOnClick = {
if (!detailModel.isNavigating) {
@ -67,15 +61,8 @@ class ArtistDetailFragment : DetailFragment() {
binding.lifecycleOwner = this
binding.detailToolbar.apply {
inflateMenu(R.menu.menu_artist_detail)
setNavigationOnClickListener {
findNavController().navigateUp()
}
setOnMenuItemClickListener {
when (it.itemId) {
setupToolbar(R.menu.menu_artist_actions) {
when (it) {
R.id.action_shuffle -> {
playbackModel.playArtist(
detailModel.currentArtist.value!!,
@ -96,10 +83,9 @@ class ArtistDetailFragment : DetailFragment() {
else -> false
}
}
}
binding.detailRecycler.apply {
adapter = albumAdapter
adapter = detailAdapter
setHasFixedSize(true)
}
@ -112,7 +98,7 @@ class ArtistDetailFragment : DetailFragment() {
it.addAll(mode.getSortedAlbumList(detailModel.currentArtist.value!!.albums))
}
albumAdapter.submitList(data)
detailAdapter.submitList(data)
}
playbackModel.navToItem.observe(viewLifecycleOwner) {

View file

@ -3,20 +3,26 @@ package org.oxycblt.auxio.detail
import android.os.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.annotation.MenuRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.memberBinding
/**
* A Base [Fragment] implementing a [OnBackPressedCallback] so that Auxio will navigate upwards
* instead of out of the app if a Detail Fragment is currently open. Also carries the
* multi-navigation fix.
* TODO: Migrate to a more powerful/efficient CoordinatorLayout instead of NestedScrollView
* A Base [Fragment] implementing the base features shared across all detail fragments.
* TODO: Add custom artist images
* TODO: Add playing item highlighting
* @author OxygenCobalt
*/
abstract class DetailFragment : Fragment() {
protected val detailModel: DetailViewModel by activityViewModels()
protected val playbackModel: PlaybackViewModel by activityViewModels()
protected val binding: FragmentDetailBinding by memberBinding(
FragmentDetailBinding::inflate
)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
@ -34,6 +40,25 @@ abstract class DetailFragment : Fragment() {
callback.isEnabled = false
}
/**
* Shortcut method for doing setup of the detail toolbar.
*/
protected fun setupToolbar(@MenuRes menu: Int, onMenuClick: (id: Int) -> Boolean) {
binding.detailToolbar.apply {
inflateMenu(menu)
setNavigationOnClickListener {
findNavController().navigateUp()
}
setOnMenuItemClickListener {
onMenuClick(it.itemId)
}
}
}
// Override the back button so that going back will only exit the detail fragments instead of
// the entire app.
private val callback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {

View file

@ -5,17 +5,13 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.adapters.GenreSongAdapter
import org.oxycblt.auxio.detail.adapters.GenreDetailAdapter
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.isLandscape
import org.oxycblt.auxio.ui.setupGenreSongActions
@ -25,17 +21,13 @@ import org.oxycblt.auxio.ui.setupGenreSongActions
* @author OxygenCobalt
*/
class GenreDetailFragment : DetailFragment() {
private val args: GenreDetailFragmentArgs by navArgs()
private val playbackModel: PlaybackViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val binding = FragmentDetailBinding.inflate(inflater)
// If DetailViewModel isn't already storing the genre, get it from MusicStore
// using the ID given by the navigation arguments
if (detailModel.currentGenre.value == null ||
@ -48,7 +40,7 @@ class GenreDetailFragment : DetailFragment() {
)
}
val songAdapter = GenreSongAdapter(
val detailAdapter = GenreDetailAdapter(
detailModel, viewLifecycleOwner,
doOnClick = {
playbackModel.playSong(it, PlaybackMode.IN_GENRE)
@ -64,14 +56,8 @@ class GenreDetailFragment : DetailFragment() {
binding.lifecycleOwner = this
binding.detailToolbar.apply {
inflateMenu(R.menu.menu_songs)
setNavigationOnClickListener {
findNavController().navigateUp()
}
setOnMenuItemClickListener {
when (it.itemId) {
setupToolbar(R.menu.menu_genre_actions) {
when (it) {
R.id.action_shuffle -> {
playbackModel.playGenre(
detailModel.currentGenre.value!!,
@ -84,10 +70,9 @@ class GenreDetailFragment : DetailFragment() {
else -> false
}
}
}
binding.detailRecycler.apply {
adapter = songAdapter
adapter = detailAdapter
setHasFixedSize(true)
if (isLandscape(resources)) {
@ -110,7 +95,7 @@ class GenreDetailFragment : DetailFragment() {
it.addAll(mode.getSortedSongList(detailModel.currentGenre.value!!.songs))
}
songAdapter.submitList(data)
detailAdapter.submitList(data)
}
logD("Fragment created.")

View file

@ -0,0 +1,85 @@
package org.oxycblt.auxio.detail.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemAlbumHeaderBinding
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
import org.oxycblt.auxio.ui.disable
/**
* An adapter for displaying the details and [Song]s of an [Album]
*/
class AlbumDetailAdapter(
private val detailModel: DetailViewModel,
private val lifecycleOwner: LifecycleOwner,
private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (data: Song, view: View) -> Unit
) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback()) {
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is Album -> ALBUM_HEADER_ITEM_TYPE
is Song -> ALBUM_SONG_ITEM_TYPE
else -> -1
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ALBUM_HEADER_ITEM_TYPE -> AlbumHeaderViewHolder(
ItemAlbumHeaderBinding.inflate(LayoutInflater.from(parent.context))
)
ALBUM_SONG_ITEM_TYPE -> AlbumSongViewHolder(
ItemAlbumSongBinding.inflate(LayoutInflater.from(parent.context))
)
else -> error("Invalid ViewHolder item type $viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (val item = getItem(position)) {
is Album -> (holder as AlbumHeaderViewHolder).bind(item)
is Song -> (holder as AlbumSongViewHolder).bind(item)
}
}
inner class AlbumHeaderViewHolder(
private val binding: ItemAlbumHeaderBinding
) : BaseViewHolder<Album>(binding, null, null) {
override fun onBind(data: Album) {
binding.album = data
binding.detailModel = detailModel
binding.lifecycleOwner = lifecycleOwner
if (data.songs.size < 2) {
binding.albumSortButton.disable()
}
}
}
inner class AlbumSongViewHolder(
private val binding: ItemAlbumSongBinding,
) : BaseViewHolder<Song>(binding, doOnClick, doOnLongClick) {
override fun onBind(data: Song) {
binding.song = data
binding.songName.requestLayout()
}
}
companion object {
const val ALBUM_HEADER_ITEM_TYPE = 0xA024
const val ALBUM_SONG_ITEM_TYPE = 0xA025
}
}

View file

@ -1,57 +0,0 @@
package org.oxycblt.auxio.detail.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.toColor
/**
* An adapter for displaying the [Song]s of an album.
*/
class AlbumSongAdapter(
private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (data: Song, view: View) -> Unit
) : ListAdapter<Song, AlbumSongAdapter.ViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemAlbumSongBinding.inflate(LayoutInflater.from(parent.context))
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
// Generic ViewHolder for a song
inner class ViewHolder(
private val binding: ItemAlbumSongBinding,
) : BaseViewHolder<Song>(binding, doOnClick, doOnLongClick) {
private val normalColor = binding.songName.currentTextColor
private val inactiveColor = binding.songTrack.currentTextColor
override fun onBind(data: Song) {
binding.song = data
binding.songName.requestLayout()
}
fun setPlaying(context: Context) {
val accentColor = accent.first.toColor(context)
binding.songName.setTextColor(accentColor)
binding.songTrack.setTextColor(accentColor)
}
fun removePlaying() {
binding.songName.setTextColor(normalColor)
binding.songTrack.setTextColor(inactiveColor)
}
}
}

View file

@ -19,7 +19,7 @@ import org.oxycblt.auxio.ui.disable
/**
* An adapter for displaying the [Album]s of an artist.
*/
class ArtistAlbumAdapter(
class ArtistDetailAdapter(
private val detailModel: DetailViewModel,
private val lifecycleOwner: LifecycleOwner,
private val doOnClick: (data: Album) -> Unit,

View file

@ -19,7 +19,7 @@ import org.oxycblt.auxio.ui.disable
/**
* An adapter for displaying the [Song]s of a genre.
*/
class GenreSongAdapter(
class GenreDetailAdapter(
private val detailModel: DetailViewModel,
private val lifecycleOwner: LifecycleOwner,
private val doOnClick: (data: Song) -> Unit,

View file

@ -9,7 +9,6 @@ import android.widget.ImageButton
import android.widget.TextView
import androidx.databinding.BindingAdapter
import org.oxycblt.auxio.R
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.recycler.SortMode
/**
@ -174,7 +173,6 @@ fun TextView.bindAlbumYear(album: Album) {
* Bind the [SortMode] icon for an ImageButton.
*/
@BindingAdapter("sortIcon")
fun ImageButton.bindSortIcon(data: SortMode) {
logD("YOU STUPID FUCKING RETARD JUST FUNCITON")
setImageResource(data.iconRes)
fun ImageButton.bindSortIcon(mode: SortMode) {
setImageResource(mode.iconRes)
}

View file

@ -196,7 +196,7 @@ fun PopupMenu.setupAlbumActions(
else -> false
}
}
inflateAndShow(R.menu.menu_album_detail)
inflateAndShow(R.menu.menu_album_actions)
}
/**
@ -220,7 +220,7 @@ fun PopupMenu.setupArtistActions(artist: Artist, playbackModel: PlaybackViewMode
else -> false
}
}
inflateAndShow(R.menu.menu_artist_detail)
inflateAndShow(R.menu.menu_artist_actions)
}
/**
@ -239,7 +239,7 @@ fun PopupMenu.setupGenreActions(genre: Genre, playbackModel: PlaybackViewModel)
else -> false
}
}
inflateAndShow(R.menu.menu_genre_detail)
inflateAndShow(R.menu.menu_genre_actions)
}
/**

View file

@ -1,139 +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.AlbumDetailFragment">
<data>
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
<variable
name="album"
type="org.oxycblt.auxio.music.Album" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/album_toolbar"
style="@style/Toolbar.Style.Icon"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:menu="@menu/menu_album_detail"
app:title="@string/label_library" />
<androidx.core.widget.NestedScrollView
android:id="@+id/nested_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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_mid_huge"
android:layout_height="@dimen/size_cover_mid_huge"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium"
android:contentDescription="@{@string/description_album_cover(album.name)}"
app:coverArt="@{album}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_album" />
<TextView
android:id="@+id/album_name"
style="@style/DetailTitleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginEnd="@dimen/margin_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="parent"
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/margin_medium"
android:background="@drawable/ui_ripple"
android:clickable="true"
android:focusable="true"
android:onClick="@{() -> detailModel.doNavToParent()}"
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/margin_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:albumDetails="@{album}"
app:layout_constraintBottom_toTopOf="@+id/album_song_header"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toBottomOf="@+id/album_artist"
tools:text="2020 / 10 Songs / 16:16" />
<TextView
android:id="@+id/album_song_header"
style="@style/HeaderText"
android:layout_marginTop="@dimen/margin_medium"
android:text="@string/label_songs"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_cover" />
<ImageButton
android:id="@+id/album_sort_button"
style="@style/HeaderAction"
android:contentDescription="@string/description_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"
tools:src="@drawable/ic_sort_numeric_down" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/album_song_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:nestedScrollingEnabled="false"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_song_header"
app:spanCount="2"
tools:itemCount="6"
tools:listitem="@layout/item_album_song" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</layout>

View file

@ -0,0 +1,99 @@
<?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="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="album"
type="org.oxycblt.auxio.music.Album" />
</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_mid_huge"
android:layout_height="@dimen/size_cover_mid_huge"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium"
android:contentDescription="@{@string/description_album_cover(album.name)}"
app:coverArt="@{album}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_album" />
<TextView
android:id="@+id/album_name"
style="@style/DetailTitleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginEnd="@dimen/margin_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="parent"
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/margin_medium"
android:background="@drawable/ui_ripple"
android:clickable="true"
android:focusable="true"
android:onClick="@{() -> detailModel.doNavToParent()}"
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/margin_medium"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
app:albumDetails="@{album}"
app:layout_constraintBottom_toTopOf="@+id/album_song_header"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toBottomOf="@+id/album_artist"
tools:text="2020 / 10 Songs / 16:16" />
<TextView
android:id="@+id/album_song_header"
style="@style/HeaderText"
android:layout_marginTop="@dimen/margin_medium"
android:text="@string/label_songs"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_cover" />
<ImageButton
android:id="@+id/album_sort_button"
style="@style/HeaderAction"
android:contentDescription="@string/description_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

@ -84,10 +84,10 @@
style="@style/HeaderAction"
android:contentDescription="@string/description_sort_button"
android:onClick="@{() -> detailModel.incrementArtistSortMode()}"
app:sortIcon="@{detailModel.artistSortMode}"
app:layout_constraintBottom_toBottomOf="@+id/artist_album_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/artist_album_header"
app:sortIcon="@{detailModel.artistSortMode}"
tools:src="@drawable/ic_sort_numeric_down" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<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>
@ -17,9 +17,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
android:layout_height="match_parent">
<ImageView
android:id="@+id/genre_image"
@ -53,9 +51,9 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:text="@{@plurals/format_song_count(genre.songs.size(), genre.songs.size())}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary"
android:text="@{@plurals/format_song_count(genre.songs.size(), genre.songs.size())}"
app:layout_constraintBottom_toTopOf="@+id/genre_duration"
app:layout_constraintStart_toEndOf="@+id/genre_image"
app:layout_constraintTop_toBottomOf="@+id/genre_name"
@ -89,8 +87,8 @@
android:onClick="@{() -> detailModel.incrementGenreSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/genre_song_header"
app:layout_constraintEnd_toEndOf="parent"
app:sortIcon="@{detailModel.genreSortMode}"
app:layout_constraintTop_toTopOf="@+id/genre_song_header"
app:sortIcon="@{detailModel.genreSortMode}"
tools:src="@drawable/ic_sort_alpha_down" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,135 +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.AlbumDetailFragment">
<data>
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
<variable
name="album"
type="org.oxycblt.auxio.music.Album" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/album_toolbar"
style="@style/Toolbar.Style.Icon"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:menu="@menu/menu_album_detail"
app:title="@string/label_library" />
<androidx.core.widget.NestedScrollView
android:id="@+id/nested_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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_marginTop="@dimen/margin_medium"
android:contentDescription="@{@string/description_album_cover(album.name)}"
app:coverArt="@{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/DetailTitleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium"
android:layout_marginEnd="@dimen/margin_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/margin_medium"
android:background="@drawable/ui_ripple"
android:clickable="true"
android:focusable="true"
android:onClick="@{() -> detailModel.doNavToParent()}"
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/margin_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" />
<TextView
android:id="@+id/album_song_header"
style="@style/HeaderText"
android:layout_marginTop="@dimen/margin_medium"
android:text="@string/label_songs"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_details" />
<ImageButton
android:id="@+id/album_sort_button"
style="@style/HeaderAction"
android:contentDescription="@string/description_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"
tools:src="@drawable/ic_sort_numeric_down" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/album_song_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:nestedScrollingEnabled="false"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_song_header"
tools:itemCount="4"
tools:listitem="@layout/item_album_song" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</layout>

View file

@ -14,9 +14,9 @@
style="@style/Toolbar.Style.Icon"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:title="@string/label_library"
app:contentInsetStartWithNavigation="0dp"
tools:menu="@menu/menu_artist_detail"/>
app:title="@string/label_library"
tools:menu="@menu/menu_artist_actions" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/detail_recycler"

View file

@ -0,0 +1,96 @@
<?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="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="album"
type="org.oxycblt.auxio.music.Album" />
</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_marginTop="@dimen/margin_medium"
android:contentDescription="@{@string/description_album_cover(album.name)}"
app:coverArt="@{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/DetailTitleText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium"
android:layout_marginEnd="@dimen/margin_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/margin_medium"
android:background="@drawable/ui_ripple"
android:clickable="true"
android:focusable="true"
android:onClick="@{() -> detailModel.doNavToParent()}"
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/margin_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" />
<TextView
android:id="@+id/album_song_header"
style="@style/HeaderText"
android:layout_marginTop="@dimen/margin_medium"
android:text="@string/label_songs"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_details" />
<ImageButton
android:id="@+id/album_sort_button"
style="@style/HeaderAction"
android:contentDescription="@string/description_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

@ -82,10 +82,10 @@
style="@style/HeaderAction"
android:contentDescription="@string/description_sort_button"
android:onClick="@{() -> detailModel.incrementArtistSortMode()}"
app:sortIcon="@{detailModel.artistSortMode}"
app:layout_constraintBottom_toBottomOf="@+id/artist_album_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/artist_album_header"
app:sortIcon="@{detailModel.artistSortMode}"
tools:src="@drawable/ic_sort_numeric_down" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<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>
@ -17,9 +17,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
android:layout_height="match_parent">
<ImageView
android:id="@+id/genre_image"

View file

@ -52,7 +52,7 @@
android:id="@+id/album_detail_fragment"
android:name="org.oxycblt.auxio.detail.AlbumDetailFragment"
android:label="AlbumDetailFragment"
tools:layout="@layout/fragment_album_detail">
tools:layout="@layout/fragment_detail">
<argument
android:name="albumId"
app:argType="long" />