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 ## 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. 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 - Better music loading system
- Improved genre/artist/album UIs - Improved genre/artist/album UIs
- Dedicated search tab - New search setup
- Swipe-to-next-track function - Swipe-to-next-track function
- Artist Images - Artist Images
- Black theme - Black theme

View file

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

View file

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

View file

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

View file

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

View file

@ -3,20 +3,26 @@ package org.oxycblt.auxio.detail
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.annotation.MenuRes
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import 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 * A Base [Fragment] implementing the base features shared across all detail fragments.
* 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
* TODO: Add custom artist images * TODO: Add custom artist images
* TODO: Add playing item highlighting
* @author OxygenCobalt * @author OxygenCobalt
*/ */
abstract class DetailFragment : Fragment() { abstract class DetailFragment : Fragment() {
protected val detailModel: DetailViewModel by activityViewModels() 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback) requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
@ -34,6 +40,25 @@ abstract class DetailFragment : Fragment() {
callback.isEnabled = false 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) { private val callback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {

View file

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

View file

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

View file

@ -196,7 +196,7 @@ fun PopupMenu.setupAlbumActions(
else -> false 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 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 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" style="@style/HeaderAction"
android:contentDescription="@string/description_sort_button" android:contentDescription="@string/description_sort_button"
android:onClick="@{() -> detailModel.incrementArtistSortMode()}" android:onClick="@{() -> detailModel.incrementArtistSortMode()}"
app:sortIcon="@{detailModel.artistSortMode}"
app:layout_constraintBottom_toBottomOf="@+id/artist_album_header" app:layout_constraintBottom_toBottomOf="@+id/artist_album_header"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/artist_album_header" app:layout_constraintTop_toTopOf="@+id/artist_album_header"
app:sortIcon="@{detailModel.artistSortMode}"
tools:src="@drawable/ic_sort_numeric_down" /> tools:src="@drawable/ic_sort_numeric_down" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools" <layout xmlns:android="http://schemas.android.com/apk/res/android"
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">
<data> <data>
@ -17,9 +17,7 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:animateLayoutChanges="true"
android:orientation="vertical">
<ImageView <ImageView
android:id="@+id/genre_image" android:id="@+id/genre_image"
@ -53,9 +51,9 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium" android:layout_marginStart="@dimen/margin_medium"
android:text="@{@plurals/format_song_count(genre.songs.size(), genre.songs.size())}"
android:textAppearance="?android:attr/textAppearanceListItem" android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorSecondary" 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_constraintBottom_toTopOf="@+id/genre_duration"
app:layout_constraintStart_toEndOf="@+id/genre_image" app:layout_constraintStart_toEndOf="@+id/genre_image"
app:layout_constraintTop_toBottomOf="@+id/genre_name" app:layout_constraintTop_toBottomOf="@+id/genre_name"
@ -89,8 +87,8 @@
android:onClick="@{() -> detailModel.incrementGenreSortMode()}" android:onClick="@{() -> detailModel.incrementGenreSortMode()}"
app:layout_constraintBottom_toBottomOf="@+id/genre_song_header" app:layout_constraintBottom_toBottomOf="@+id/genre_song_header"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:sortIcon="@{detailModel.genreSortMode}"
app:layout_constraintTop_toTopOf="@+id/genre_song_header" app:layout_constraintTop_toTopOf="@+id/genre_song_header"
app:sortIcon="@{detailModel.genreSortMode}"
tools:src="@drawable/ic_sort_alpha_down" /> tools:src="@drawable/ic_sort_alpha_down" />
</androidx.constraintlayout.widget.ConstraintLayout> </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" style="@style/Toolbar.Style.Icon"
android:background="?android:attr/windowBackground" android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal" android:elevation="@dimen/elevation_normal"
app:title="@string/label_library"
app:contentInsetStartWithNavigation="0dp" app:contentInsetStartWithNavigation="0dp"
tools:menu="@menu/menu_artist_detail"/> app:title="@string/label_library"
tools:menu="@menu/menu_artist_actions" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/detail_recycler" 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" style="@style/HeaderAction"
android:contentDescription="@string/description_sort_button" android:contentDescription="@string/description_sort_button"
android:onClick="@{() -> detailModel.incrementArtistSortMode()}" android:onClick="@{() -> detailModel.incrementArtistSortMode()}"
app:sortIcon="@{detailModel.artistSortMode}"
app:layout_constraintBottom_toBottomOf="@+id/artist_album_header" app:layout_constraintBottom_toBottomOf="@+id/artist_album_header"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/artist_album_header" app:layout_constraintTop_toTopOf="@+id/artist_album_header"
app:sortIcon="@{detailModel.artistSortMode}"
tools:src="@drawable/ic_sort_numeric_down" /> tools:src="@drawable/ic_sort_numeric_down" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

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

View file

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