From 46fa300252f810e77bd3951baa8f42f783f2fcbc Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Tue, 29 Dec 2020 09:03:14 -0700 Subject: [PATCH] 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. --- README.md | 4 +- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 19 ++- .../auxio/detail/AlbumDetailFragment.kt | 134 ++++++----------- .../auxio/detail/ArtistDetailFragment.kt | 56 +++---- .../oxycblt/auxio/detail/DetailFragment.kt | 33 ++++- .../auxio/detail/GenreDetailFragment.kt | 41 ++---- .../detail/adapters/AlbumDetailAdapter.kt | 85 +++++++++++ .../auxio/detail/adapters/AlbumSongAdapter.kt | 57 ------- ...AlbumAdapter.kt => ArtistDetailAdapter.kt} | 2 +- ...reSongAdapter.kt => GenreDetailAdapter.kt} | 2 +- .../org/oxycblt/auxio/music/MusicUtils.kt | 6 +- .../org/oxycblt/auxio/ui/InterfaceUtils.kt | 6 +- .../res/layout-land/fragment_album_detail.xml | 139 ------------------ .../res/layout-land/item_album_header.xml | 99 +++++++++++++ .../res/layout-land/item_artist_header.xml | 2 +- .../res/layout-land/item_genre_header.xml | 14 +- .../main/res/layout/fragment_album_detail.xml | 135 ----------------- app/src/main/res/layout/fragment_detail.xml | 4 +- app/src/main/res/layout/item_album_header.xml | 96 ++++++++++++ .../main/res/layout/item_artist_header.xml | 2 +- app/src/main/res/layout/item_genre_header.xml | 10 +- ...lbum_detail.xml => menu_album_actions.xml} | 0 ...ist_detail.xml => menu_artist_actions.xml} | 0 ...enre_detail.xml => menu_genre_actions.xml} | 0 app/src/main/res/navigation/nav_explore.xml | 2 +- 26 files changed, 418 insertions(+), 532 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumDetailAdapter.kt delete mode 100644 app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumSongAdapter.kt rename app/src/main/java/org/oxycblt/auxio/detail/adapters/{ArtistAlbumAdapter.kt => ArtistDetailAdapter.kt} (99%) rename app/src/main/java/org/oxycblt/auxio/detail/adapters/{GenreSongAdapter.kt => GenreDetailAdapter.kt} (99%) delete mode 100644 app/src/main/res/layout-land/fragment_album_detail.xml create mode 100644 app/src/main/res/layout-land/item_album_header.xml delete mode 100644 app/src/main/res/layout/fragment_album_detail.xml create mode 100644 app/src/main/res/layout/item_album_header.xml rename app/src/main/res/menu/{menu_album_detail.xml => menu_album_actions.xml} (100%) rename app/src/main/res/menu/{menu_artist_detail.xml => menu_artist_actions.xml} (100%) rename app/src/main/res/menu/{menu_genre_detail.xml => menu_genre_actions.xml} (100%) diff --git a/README.md b/README.md index 3bc6f4907..085974bf1 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/build.gradle b/app/build.gradle index 2e69f1b8d..478ffc4f3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 83315b942..037771b66 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,26 +1,25 @@ - + + android:theme="@style/Theme.Base"> @@ -32,12 +31,12 @@ \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 4931f8e28..64ceee535 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -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,59 +54,43 @@ 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() - } + setupToolbar(R.menu.menu_album_actions) { + when (it) { + R.id.action_shuffle -> { + playbackModel.playAlbum( + detailModel.currentAlbum.value!!, true + ) - setOnMenuItemClickListener { - 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 + true } + 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 { - 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(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() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index 1411f4e0e..9b1cff939 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -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,39 +61,31 @@ class ArtistDetailFragment : DetailFragment() { binding.lifecycleOwner = this - binding.detailToolbar.apply { - inflateMenu(R.menu.menu_artist_detail) - - setNavigationOnClickListener { - findNavController().navigateUp() - } - - setOnMenuItemClickListener { - when (it.itemId) { - R.id.action_shuffle -> { - playbackModel.playArtist( - detailModel.currentArtist.value!!, - true - ) - + setupToolbar(R.menu.menu_artist_actions) { + when (it) { + R.id.action_shuffle -> { + playbackModel.playArtist( + detailModel.currentArtist.value!!, true - } + ) - R.id.action_play_albums -> { - playbackModel.playArtist( - detailModel.currentArtist.value!!, false - ) - - true - } - - else -> false + true } + + R.id.action_play_albums -> { + playbackModel.playArtist( + detailModel.currentArtist.value!!, false + ) + + true + } + + 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) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt index ce1f70570..b1bdd0478 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -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() { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index 1c53bb3d9..7debf9571 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -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,30 +56,23 @@ class GenreDetailFragment : DetailFragment() { binding.lifecycleOwner = this - binding.detailToolbar.apply { - inflateMenu(R.menu.menu_songs) - setNavigationOnClickListener { - findNavController().navigateUp() - } - - setOnMenuItemClickListener { - when (it.itemId) { - R.id.action_shuffle -> { - playbackModel.playGenre( - detailModel.currentGenre.value!!, - true - ) - + setupToolbar(R.menu.menu_genre_actions) { + when (it) { + R.id.action_shuffle -> { + playbackModel.playGenre( + detailModel.currentGenre.value!!, true - } + ) - else -> false + true } + + 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.") diff --git a/app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumDetailAdapter.kt new file mode 100644 index 000000000..d4aa5d971 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumDetailAdapter.kt @@ -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(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(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(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 + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumSongAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumSongAdapter.kt deleted file mode 100644 index 59bd1a091..000000000 --- a/app/src/main/java/org/oxycblt/auxio/detail/adapters/AlbumSongAdapter.kt +++ /dev/null @@ -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(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(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) - } - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/detail/adapters/ArtistAlbumAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/adapters/ArtistDetailAdapter.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/detail/adapters/ArtistAlbumAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/detail/adapters/ArtistDetailAdapter.kt index f86d27478..708cc169e 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/adapters/ArtistAlbumAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/adapters/ArtistDetailAdapter.kt @@ -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, diff --git a/app/src/main/java/org/oxycblt/auxio/detail/adapters/GenreSongAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/adapters/GenreDetailAdapter.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/detail/adapters/GenreSongAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/detail/adapters/GenreDetailAdapter.kt index 9f57b5f95..210b1d557 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/adapters/GenreSongAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/adapters/GenreDetailAdapter.kt @@ -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, diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt index ebeb85d3a..8352d50c8 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt @@ -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) } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt index 9402b59ca..c09c34fe1 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt @@ -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) } /** diff --git a/app/src/main/res/layout-land/fragment_album_detail.xml b/app/src/main/res/layout-land/fragment_album_detail.xml deleted file mode 100644 index 70dd928e8..000000000 --- a/app/src/main/res/layout-land/fragment_album_detail.xml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-land/item_album_header.xml b/app/src/main/res/layout-land/item_album_header.xml new file mode 100644 index 000000000..a736160bd --- /dev/null +++ b/app/src/main/res/layout-land/item_album_header.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/item_artist_header.xml b/app/src/main/res/layout-land/item_artist_header.xml index 85552589d..9faa2588e 100644 --- a/app/src/main/res/layout-land/item_artist_header.xml +++ b/app/src/main/res/layout-land/item_artist_header.xml @@ -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" /> diff --git a/app/src/main/res/layout-land/item_genre_header.xml b/app/src/main/res/layout-land/item_genre_header.xml index 4af287b0f..1ae2c3030 100644 --- a/app/src/main/res/layout-land/item_genre_header.xml +++ b/app/src/main/res/layout-land/item_genre_header.xml @@ -1,7 +1,7 @@ - + @@ -17,9 +17,7 @@ + android:layout_height="match_parent"> diff --git a/app/src/main/res/layout/fragment_album_detail.xml b/app/src/main/res/layout/fragment_album_detail.xml deleted file mode 100644 index 5c363e2e2..000000000 --- a/app/src/main/res/layout/fragment_album_detail.xml +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_detail.xml b/app/src/main/res/layout/fragment_detail.xml index b871cb253..72a020057 100644 --- a/app/src/main/res/layout/fragment_detail.xml +++ b/app/src/main/res/layout/fragment_detail.xml @@ -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" /> + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_artist_header.xml b/app/src/main/res/layout/item_artist_header.xml index 0d9dbb0af..08a865f96 100644 --- a/app/src/main/res/layout/item_artist_header.xml +++ b/app/src/main/res/layout/item_artist_header.xml @@ -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" /> diff --git a/app/src/main/res/layout/item_genre_header.xml b/app/src/main/res/layout/item_genre_header.xml index a01681e63..1fa61f14c 100644 --- a/app/src/main/res/layout/item_genre_header.xml +++ b/app/src/main/res/layout/item_genre_header.xml @@ -1,7 +1,7 @@ - + @@ -17,9 +17,7 @@ + android:layout_height="match_parent"> + tools:layout="@layout/fragment_detail">