detail: default to "no disc" instead of "disc 1"

Default tracks without a disc to a group called "No disc" instead of
disc 1.

This should reduce confusion on the user end, as it will make improper
taggings more apparent instead of simply degrading to a werid sort
ordering.

Resolves #405.
This commit is contained in:
Alexander Capehart 2023-05-25 12:01:41 -06:00
parent 4210a8d247
commit 8939d341e6
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 30 additions and 18 deletions

View file

@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.detail.list.DiscHeader
import org.oxycblt.auxio.detail.list.EditHeader import org.oxycblt.auxio.detail.list.EditHeader
import org.oxycblt.auxio.detail.list.SortHeader import org.oxycblt.auxio.detail.list.SortHeader
import org.oxycblt.auxio.list.BasicHeader import org.oxycblt.auxio.list.BasicHeader
@ -46,7 +47,6 @@ import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.info.Disc
import org.oxycblt.auxio.music.info.ReleaseType import org.oxycblt.auxio.music.info.ReleaseType
import org.oxycblt.auxio.music.metadata.AudioProperties import org.oxycblt.auxio.music.metadata.AudioProperties
import org.oxycblt.auxio.playback.PlaybackSettings import org.oxycblt.auxio.playback.PlaybackSettings
@ -409,12 +409,11 @@ constructor(
// To create a good user experience regarding disc numbers, we group the album's // To create a good user experience regarding disc numbers, we group the album's
// songs up by disc and then delimit the groups by a disc header. // songs up by disc and then delimit the groups by a disc header.
val songs = albumSongSort.songs(album.songs) val songs = albumSongSort.songs(album.songs)
// Songs without disc tags become part of Disc 1. val byDisc = songs.groupBy { it.disc }
val byDisc = songs.groupBy { it.disc ?: Disc(1, null) }
if (byDisc.size > 1) { if (byDisc.size > 1) {
logD("Album has more than one disc, interspersing headers") logD("Album has more than one disc, interspersing headers")
for (entry in byDisc.entries) { for (entry in byDisc.entries) {
list.add(entry.key) list.add(DiscHeader(entry.key))
list.addAll(entry.value) list.addAll(entry.value)
} }
} else { } else {

View file

@ -49,14 +49,14 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
override fun getItemViewType(position: Int) = override fun getItemViewType(position: Int) =
when (getItem(position)) { when (getItem(position)) {
// Support sub-headers for each disc, and special album songs. // Support sub-headers for each disc, and special album songs.
is Disc -> DiscViewHolder.VIEW_TYPE is DiscHeader -> DiscHeaderViewHolder.VIEW_TYPE
is Song -> AlbumSongViewHolder.VIEW_TYPE is Song -> AlbumSongViewHolder.VIEW_TYPE
else -> super.getItemViewType(position) else -> super.getItemViewType(position)
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
when (viewType) { when (viewType) {
DiscViewHolder.VIEW_TYPE -> DiscViewHolder.from(parent) DiscHeaderViewHolder.VIEW_TYPE -> DiscHeaderViewHolder.from(parent)
AlbumSongViewHolder.VIEW_TYPE -> AlbumSongViewHolder.from(parent) AlbumSongViewHolder.VIEW_TYPE -> AlbumSongViewHolder.from(parent)
else -> super.onCreateViewHolder(parent, viewType) else -> super.onCreateViewHolder(parent, viewType)
} }
@ -64,7 +64,7 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
super.onBindViewHolder(holder, position) super.onBindViewHolder(holder, position)
when (val item = getItem(position)) { when (val item = getItem(position)) {
is Disc -> (holder as DiscViewHolder).bind(item) is DiscHeader -> (holder as DiscHeaderViewHolder).bind(item)
is Song -> (holder as AlbumSongViewHolder).bind(item, listener) is Song -> (holder as AlbumSongViewHolder).bind(item, listener)
} }
} }
@ -76,7 +76,7 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
override fun areContentsTheSame(oldItem: Item, newItem: Item) = override fun areContentsTheSame(oldItem: Item, newItem: Item) =
when { when {
oldItem is Disc && newItem is Disc -> oldItem is Disc && newItem is Disc ->
DiscViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) DiscHeaderViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
oldItem is Song && newItem is Song -> oldItem is Song && newItem is Song ->
AlbumSongViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) AlbumSongViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem)
@ -88,23 +88,35 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
} }
/** /**
* A [RecyclerView.ViewHolder] that displays a [Disc] to delimit different disc groups. Use [from] * A wrapper around [Disc] signifying that a header should be shown for a disc group.
* to create an instance. * @author Alexander Capehart (OxygenCobalt)
*/
data class DiscHeader(val inner: Disc?) : Item
/**
* A [RecyclerView.ViewHolder] that displays a [DiscHeader] to delimit different disc groups. Use
* [from] to create an instance.
* *
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
private class DiscViewHolder(private val binding: ItemDiscHeaderBinding) : private class DiscHeaderViewHolder(private val binding: ItemDiscHeaderBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
* *
* @param disc The new [disc] to bind. * @param discHeader The new [DiscHeader] to bind.
*/ */
fun bind(disc: Disc) { fun bind(discHeader: DiscHeader) {
binding.discNumber.text = binding.context.getString(R.string.fmt_disc_no, disc.number) val disc = discHeader.inner
binding.discName.apply { if (disc != null) {
text = disc.name binding.discNumber.text = binding.context.getString(R.string.fmt_disc_no, disc.number)
isGone = disc.name == null binding.discName.apply {
text = disc.name
isGone = text == null
}
} else {
binding.discNumber.text = binding.context.getString(R.string.def_disc)
binding.discName.isGone = true
} }
} }
@ -119,7 +131,7 @@ private class DiscViewHolder(private val binding: ItemDiscHeaderBinding) :
* @return A new instance. * @return A new instance.
*/ */
fun from(parent: View) = fun from(parent: View) =
DiscViewHolder(ItemDiscHeaderBinding.inflate(parent.context.inflater)) DiscHeaderViewHolder(ItemDiscHeaderBinding.inflate(parent.context.inflater))
/** A comparator that can be used with DiffUtil. */ /** A comparator that can be used with DiffUtil. */
val DIFF_CALLBACK = val DIFF_CALLBACK =

View file

@ -332,6 +332,7 @@
<string name="def_artist">Unknown artist</string> <string name="def_artist">Unknown artist</string>
<string name="def_genre">Unknown genre</string> <string name="def_genre">Unknown genre</string>
<string name="def_date">No date</string> <string name="def_date">No date</string>
<string name="def_disc">No disc</string>
<string name="def_track">No track</string> <string name="def_track">No track</string>
<string name="def_song_count">No songs</string> <string name="def_song_count">No songs</string>
<string name="def_playback">No music playing</string> <string name="def_playback">No music playing</string>