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