From c522af546c406eaedad99bb723cf719f03ee2345 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Thu, 19 May 2022 16:40:42 -0600 Subject: [PATCH] detail: add full disc number support Finalize the disc number implementation within Auxio. This is probably one of the most widely-requested features outside of playlisting. This implementation also adds some more fine grained sorting modes for disc numbers in particular, which actually removes some of the quirkiness of the Sort class. Resolves #96. --- CHANGELOG.md | 1 + .../java/org/oxycblt/auxio/IntegerTable.kt | 4 ++ .../auxio/detail/AlbumDetailFragment.kt | 6 +- .../auxio/detail/ArtistDetailFragment.kt | 6 +- .../oxycblt/auxio/detail/DetailViewModel.kt | 16 ++++- .../auxio/detail/GenreDetailFragment.kt | 7 ++- .../detail/recycler/AlbumDetailAdapter.kt | 2 +- .../auxio/home/list/SongListFragment.kt | 4 ++ .../auxio/playback/system/PlaybackService.kt | 30 ++++----- .../oxycblt/auxio/settings/SettingsManager.kt | 14 ++++- .../main/java/org/oxycblt/auxio/ui/Sort.kt | 61 +++++++++++++++++-- app/src/main/res/menu/menu_detail_sort.xml | 6 ++ app/src/main/res/values/strings.xml | 3 + 13 files changed, 132 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fef16fe7..ebcb03349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## dev [v2.2.3, v2.3.0, or v3.0.0] #### What's New +- Added disc number support - Added ReplayGain support for below-reference volume tracks [i.e positive ReplayGain values] - About screen now shows counts for multiple types of library items, alongside a total duration diff --git a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt index 8969e8aaf..172dcb89f 100644 --- a/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt +++ b/app/src/main/java/org/oxycblt/auxio/IntegerTable.kt @@ -92,6 +92,10 @@ object IntegerTable { const val SORT_BY_ALBUM = 0xA10E /** Sort.ByYear */ const val SORT_BY_YEAR = 0xA10F + /** Sort.ByDisc */ + const val SORT_BY_DISC = 0xA114 + /** Sort.ByTrack */ + const val SORT_BY_TRACK = 0xA115 /** ReplayGainMode.Off */ const val REPLAY_GAIN_MODE_OFF = 0xA110 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 2e7264922..d88a9c10a 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -110,7 +110,11 @@ class AlbumDetailFragment : DetailFragment(), AlbumDetailAdapter.Listener { anchor, detailModel.albumSort, onConfirm = { detailModel.albumSort = it }, - showItem = { it == R.id.option_sort_asc }) + showItem = { + it == R.id.option_sort_asc || + it == R.id.option_sort_disc || + it == R.id.option_sort_track + }) } override fun onNavigateToArtist() { 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 534a5b818..533dffa6e 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -96,7 +96,11 @@ class ArtistDetailFragment : DetailFragment(), DetailAdapter.Listener { anchor, detailModel.artistSort, onConfirm = { detailModel.artistSort = it }, - showItem = { id -> id != R.id.option_sort_artist }) + showItem = { id -> + id != R.id.option_sort_artist && + id != R.id.option_sort_disc && + id != R.id.option_sort_track + }) } private fun handleNavigation(item: Music?) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index 649a7c56c..9c601b282 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -138,8 +138,20 @@ class DetailViewModel : ViewModel() { logD("Refreshing album data") val data = mutableListOf(album) data.add(SortHeader(id = -2, R.string.lbl_songs)) - data.add(DiscHeader(id = -3, 1)) - data.addAll(albumSort.album(album)) + + val songs = albumSort.album(album) + val byDisc = songs.groupBy { it.disc ?: 1 } + if (byDisc.size > 1) { + for (entry in byDisc.entries) { + val disc = entry.key + val discSongs = entry.value + data.add(DiscHeader(id = -2L - disc, disc)) + data.addAll(discSongs) + } + } else { + data.addAll(songs) + } + _albumData.value = data } } 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 404dffdb2..40d9d0bc5 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -22,6 +22,7 @@ import android.view.MenuItem import android.view.View 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.recycler.DetailAdapter import org.oxycblt.auxio.detail.recycler.GenreDetailAdapter @@ -90,7 +91,11 @@ class GenreDetailFragment : DetailFragment(), DetailAdapter.Listener { } override fun onShowSortMenu(anchor: View) { - showSortMenu(anchor, detailModel.genreSort, onConfirm = { detailModel.genreSort = it }) + showSortMenu( + anchor, + detailModel.genreSort, + onConfirm = { detailModel.genreSort = it }, + showItem = { it != R.id.option_sort_disc && it != R.id.option_sort_track }) } private fun handleNavigation(item: Music?) { diff --git a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt index 27e615aec..c342790c7 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/recycler/AlbumDetailAdapter.kt @@ -171,7 +171,7 @@ data class DiscHeader(override val id: Long, val disc: Int) : Item() class DiscHeaderViewHolder(private val binding: ItemDiscHeaderBinding) : BindingViewHolder(binding.root) { override fun bind(item: DiscHeader, listener: Unit) { - binding.discNo.textSafe = "Disc 1" + binding.discNo.textSafe = binding.context.getString(R.string.fmt_disc_no, item.disc) } companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index c41f4d370..1218c8aa9 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -65,6 +65,10 @@ class SongListFragment : HomeListFragment() { // Year -> Use Full Year is Sort.ByYear -> song.album.year?.toString() + + // Unreachable state + is Sort.ByDisc, + is Sort.ByTrack -> null } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 7f47b5c17..a0b3c3c08 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -114,21 +114,6 @@ class PlaybackService : } } - // --- PLAYBACKSTATEMANAGER SETUP --- - - playbackManager.addCallback(this) - if (playbackManager.isInitialized) { - loadSong(playbackManager.song) - onSeek(playbackManager.positionMs) - onPlayingChanged(playbackManager.isPlaying) - onShuffledChanged(playbackManager.isShuffled) - onRepeatChanged(playbackManager.repeatMode) - } - - // --- SETTINGSMANAGER SETUP --- - - settingsManager.addCallback(this) - // --- SYSTEM SETUP --- widgetComponent = WidgetComponent(this) @@ -150,6 +135,21 @@ class PlaybackService : registerReceiver(systemReceiver, this) } + // --- PLAYBACKSTATEMANAGER SETUP --- + + playbackManager.addCallback(this) + if (playbackManager.isInitialized) { + loadSong(playbackManager.song) + onSeek(playbackManager.positionMs) + onPlayingChanged(playbackManager.isPlaying) + onShuffledChanged(playbackManager.isShuffled) + onRepeatChanged(playbackManager.repeatMode) + } + + // --- SETTINGSMANAGER SETUP --- + + settingsManager.addCallback(this) + logD("Service created") } diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt index e2be76cc3..cab931176 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt @@ -181,9 +181,17 @@ class SettingsManager private constructor(context: Context) : /** The detail album sort mode */ var detailAlbumSort: Sort - get() = - Sort.fromIntCode(prefs.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE)) - ?: Sort.ByName(true) + get() { + var sort = + Sort.fromIntCode(prefs.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE)) + ?: Sort.ByDisc(true) + + if (sort is Sort.ByName) { + sort = Sort.ByDisc(sort.isAscending) + } + + return sort + } set(value) { prefs.edit { putInt(KEY_DETAIL_ALBUM_SORT, value.intCode) diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt index 828fddf3d..a87f26709 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt @@ -25,6 +25,7 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logW /** @@ -185,6 +186,57 @@ sealed class Sort(open val isAscending: Boolean) { } } + /** + * Sort by the disc, and then track number of an item. Only supported by [Song]. Do not use this + * in a main sorting view, as it is not assigned to a particular item ID + */ + class ByDisc(override val isAscending: Boolean) : Sort(isAscending) { + override val sortIntCode: Int + get() = IntegerTable.SORT_BY_DISC + + // Not an available option, so no ID is set + override val itemId: Int + get() = R.id.option_sort_disc + + override fun songs(songs: Collection): List { + logD(songs) + return songs.sortedWith( + MultiComparator( + compareByDynamic(NullableComparator()) { it.disc }, + compareBy(NullableComparator()) { it.track }, + compareBy(NameComparator()) { it })) + } + + override fun ascending(newIsAscending: Boolean): Sort { + return ByDisc(newIsAscending) + } + } + + /** + * Sort by the disc, and then track number of an item. Only supported by [Song]. Do not use this + * in a main sorting view, as it is not assigned to a particular item ID + */ + class ByTrack(override val isAscending: Boolean) : Sort(isAscending) { + override val sortIntCode: Int + get() = IntegerTable.SORT_BY_TRACK + + override val itemId: Int + get() = R.id.option_sort_track + + override fun songs(songs: Collection): List { + logD(songs) + return songs.sortedWith( + MultiComparator( + compareBy(NullableComparator()) { it.disc }, + compareByDynamic(NullableComparator()) { it.track }, + compareBy(NameComparator()) { it })) + } + + override fun ascending(newIsAscending: Boolean): Sort { + return ByTrack(newIsAscending) + } + } + val intCode: Int get() = sortIntCode.shl(1) or if (isAscending) 1 else 0 @@ -198,6 +250,8 @@ sealed class Sort(open val isAscending: Boolean) { R.id.option_sort_artist -> ByArtist(isAscending) R.id.option_sort_album -> ByAlbum(isAscending) R.id.option_sort_year -> ByYear(isAscending) + R.id.option_sort_disc -> ByDisc(isAscending) + R.id.option_sort_track -> ByTrack(isAscending) else -> null } } @@ -207,10 +261,7 @@ sealed class Sort(open val isAscending: Boolean) { * @see songs */ fun album(album: Album): List { - return album.songs.sortedWith( - MultiComparator( - compareByDynamic(NullableComparator()) { it.track }, - compareBy(NameComparator()) { it })) + return songs(album.songs) } /** @@ -304,6 +355,8 @@ sealed class Sort(open val isAscending: Boolean) { IntegerTable.SORT_BY_ARTIST -> ByArtist(ascending) IntegerTable.SORT_BY_ALBUM -> ByAlbum(ascending) IntegerTable.SORT_BY_YEAR -> ByYear(ascending) + IntegerTable.SORT_BY_DISC -> ByDisc(ascending) + IntegerTable.SORT_BY_TRACK -> ByTrack(ascending) else -> null } } diff --git a/app/src/main/res/menu/menu_detail_sort.xml b/app/src/main/res/menu/menu_detail_sort.xml index ad974af63..f50c115ee 100644 --- a/app/src/main/res/menu/menu_detail_sort.xml +++ b/app/src/main/res/menu/menu_detail_sort.xml @@ -13,6 +13,12 @@ + + Artist Album Year + Disc + Track Ascending Now Playing @@ -168,6 +170,7 @@ Grey + Disc %d Songs loaded: %d Albums loaded: %d Artists loaded: %d