diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d1b6ce7b..2d3137720 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,14 @@ ## dev +#### What's New +- Added support for disc subtitles + #### What's Improved - Auxio will now accept zeroed track/disc numbers in the presence of non-zero total track/disc fields. + ## 3.0.2 #### What's New diff --git a/app/src/main/java/org/oxycblt/auxio/detail/Detail.kt b/app/src/main/java/org/oxycblt/auxio/detail/Detail.kt index 1788b4ddd..310c10cfd 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/Detail.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/Detail.kt @@ -29,13 +29,6 @@ import org.oxycblt.auxio.music.storage.MimeType */ data class SortHeader(@StringRes val titleRes: Int) : Item -/** - * A header variation that delimits between disc groups. - * @param disc The disc number to be displayed on the header. - * @author Alexander Capehart (OxygenCobalt) - */ -data class DiscHeader(val disc: Int) : Item - /** * The properties of a [Song]'s file. * @param bitrateKbps The bit rate, in kilobytes-per-second. Null if it could not be parsed. 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 d51cd3734..c3b3580d3 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -37,6 +37,7 @@ import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.library.Library import org.oxycblt.auxio.music.library.Sort import org.oxycblt.auxio.music.storage.MimeType +import org.oxycblt.auxio.music.tags.Disc import org.oxycblt.auxio.music.tags.ReleaseType import org.oxycblt.auxio.playback.PlaybackSettings import org.oxycblt.auxio.util.* @@ -323,11 +324,11 @@ class DetailViewModel(application: Application) : // songs up by disc and then delimit the groups by a disc header. val songs = albumSongSort.songs(album.songs) // Songs without disc tags become part of Disc 1. - val byDisc = songs.groupBy { it.disc ?: 1 } + val byDisc = songs.groupBy { it.disc ?: Disc(1, null) } if (byDisc.size > 1) { logD("Album has more than one disc, interspersing headers") for (entry in byDisc.entries) { - data.add(DiscHeader(entry.key)) + data.add(entry.key) data.addAll(entry.value) } } else { 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 6a8611cb7..2fb514f2d 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 @@ -19,6 +19,7 @@ package org.oxycblt.auxio.detail.recycler import android.view.View import android.view.ViewGroup +import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.IntegerTable @@ -26,13 +27,13 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.ItemAlbumSongBinding import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding -import org.oxycblt.auxio.detail.DiscHeader import org.oxycblt.auxio.list.Item import org.oxycblt.auxio.list.SelectableListListener import org.oxycblt.auxio.list.adapter.SelectionIndicatorAdapter import org.oxycblt.auxio.list.adapter.SimpleDiffCallback import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.tags.Disc import org.oxycblt.auxio.playback.formatDurationMs import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.getPlural @@ -60,7 +61,7 @@ class AlbumDetailAdapter(private val listener: Listener) : DetailAdapter(listene when (getItem(position)) { // Support the Album header, sub-headers for each disc, and special album songs. is Album -> AlbumDetailViewHolder.VIEW_TYPE - is DiscHeader -> DiscHeaderViewHolder.VIEW_TYPE + is Disc -> DiscViewHolder.VIEW_TYPE is Song -> AlbumSongViewHolder.VIEW_TYPE else -> super.getItemViewType(position) } @@ -68,7 +69,7 @@ class AlbumDetailAdapter(private val listener: Listener) : DetailAdapter(listene override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) { AlbumDetailViewHolder.VIEW_TYPE -> AlbumDetailViewHolder.from(parent) - DiscHeaderViewHolder.VIEW_TYPE -> DiscHeaderViewHolder.from(parent) + DiscViewHolder.VIEW_TYPE -> DiscViewHolder.from(parent) AlbumSongViewHolder.VIEW_TYPE -> AlbumSongViewHolder.from(parent) else -> super.onCreateViewHolder(parent, viewType) } @@ -77,7 +78,7 @@ class AlbumDetailAdapter(private val listener: Listener) : DetailAdapter(listene super.onBindViewHolder(holder, position) when (val item = getItem(position)) { is Album -> (holder as AlbumDetailViewHolder).bind(item, listener) - is DiscHeader -> (holder as DiscHeaderViewHolder).bind(item) + is Disc -> (holder as DiscViewHolder).bind(item) is Song -> (holder as AlbumSongViewHolder).bind(item, listener) } } @@ -88,7 +89,7 @@ class AlbumDetailAdapter(private val listener: Listener) : DetailAdapter(listene } // The album and disc headers should be full-width in all configurations. val item = getItem(position) - return item is Album || item is DiscHeader + return item is Album || item is Disc } private companion object { @@ -99,8 +100,8 @@ class AlbumDetailAdapter(private val listener: Listener) : DetailAdapter(listene return when { oldItem is Album && newItem is Album -> AlbumDetailViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) - oldItem is DiscHeader && newItem is DiscHeader -> - DiscHeaderViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) + oldItem is Disc && newItem is Disc -> + DiscViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) oldItem is Song && newItem is Song -> AlbumSongViewHolder.DIFF_CALLBACK.areContentsTheSame(oldItem, newItem) @@ -182,18 +183,22 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite } /** - * A [RecyclerView.ViewHolder] that displays a [DiscHeader] to delimit different disc groups. Use - * [from] to create an instance. + * A [RecyclerView.ViewHolder] that displays a [Disc] to delimit different disc groups. Use [from] + * to create an instance. * @author Alexander Capehart (OxygenCobalt) */ -private class DiscHeaderViewHolder(private val binding: ItemDiscHeaderBinding) : +private class DiscViewHolder(private val binding: ItemDiscHeaderBinding) : RecyclerView.ViewHolder(binding.root) { /** * Bind new data to this instance. - * @param discHeader The new [DiscHeader] to bind. + * @param disc The new [disc] to bind. */ - fun bind(discHeader: DiscHeader) { - binding.discNo.text = binding.context.getString(R.string.fmt_disc_no, discHeader.disc) + fun bind(disc: Disc) { + binding.discNumber.text = binding.context.getString(R.string.fmt_disc_no, disc.number) + binding.discName.apply { + text = disc.name + isGone = disc.name == null + } } companion object { @@ -206,13 +211,13 @@ private class DiscHeaderViewHolder(private val binding: ItemDiscHeaderBinding) : * @return A new instance. */ fun from(parent: View) = - DiscHeaderViewHolder(ItemDiscHeaderBinding.inflate(parent.context.inflater)) + DiscViewHolder(ItemDiscHeaderBinding.inflate(parent.context.inflater)) /** A comparator that can be used with DiffUtil. */ val DIFF_CALLBACK = - object : SimpleDiffCallback() { - override fun areContentsTheSame(oldItem: DiscHeader, newItem: DiscHeader) = - oldItem.disc == newItem.disc + object : SimpleDiffCallback() { + override fun areContentsTheSame(oldItem: Disc, newItem: Disc) = + oldItem.number == newItem.number && oldItem.name == newItem.name } } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index 39aeab02d..ba7d0aee4 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -36,6 +36,7 @@ import org.oxycblt.auxio.music.parsing.parseId3GenreNames import org.oxycblt.auxio.music.parsing.parseMultiValue import org.oxycblt.auxio.music.storage.* import org.oxycblt.auxio.music.tags.Date +import org.oxycblt.auxio.music.tags.Disc import org.oxycblt.auxio.music.tags.ReleaseType import org.oxycblt.auxio.util.nonZeroOrNull import org.oxycblt.auxio.util.unlikelyToBeNull @@ -340,8 +341,8 @@ class Song constructor(raw: Raw, musicSettings: MusicSettings) : Music() { /** The track number. Will be null if no valid track number was present in the metadata. */ val track = raw.track - /** The disc number. Will be null if no valid disc number was present in the metadata. */ - val disc = raw.disc + /** The [Disc] number. Will be null if no valid disc number was present in the metadata. */ + val disc = raw.disc?.let { Disc(it, raw.subtitle) } /** The release [Date]. Will be null if no valid date was present in the metadata. */ val date = raw.date @@ -573,8 +574,10 @@ class Song constructor(raw: Raw, musicSettings: MusicSettings) : Music() { var sortName: String? = null, /** @see Song.track */ var track: Int? = null, - /** @see Song.disc */ + /** @see Disc.number */ var disc: Int? = null, + /** @See Disc.name */ + var subtitle: String? = null, /** @see Song.date */ var date: Date? = null, /** @see Album.Raw.mediaStoreId */ diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheExtractor.kt index 94531c376..9c34e2af3 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/CacheExtractor.kt @@ -186,6 +186,7 @@ private class CacheDatabase(context: Context) : append("${Columns.SORT_NAME} STRING,") append("${Columns.TRACK} INT,") append("${Columns.DISC} INT,") + append("${Columns.SUBTITLE} STRING,") append("${Columns.DATE} STRING,") append("${Columns.ALBUM_MUSIC_BRAINZ_ID} STRING,") append("${Columns.ALBUM_NAME} STRING NOT NULL,") @@ -243,6 +244,7 @@ private class CacheDatabase(context: Context) : val trackIndex = cursor.getColumnIndexOrThrow(Columns.TRACK) val discIndex = cursor.getColumnIndexOrThrow(Columns.DISC) + val subtitleIndex = cursor.getColumnIndex(Columns.SUBTITLE) val dateIndex = cursor.getColumnIndexOrThrow(Columns.DATE) val albumMusicBrainzIdIndex = @@ -281,6 +283,7 @@ private class CacheDatabase(context: Context) : raw.track = cursor.getIntOrNull(trackIndex) raw.disc = cursor.getIntOrNull(discIndex) + raw.subtitle = cursor.getStringOrNull(subtitleIndex) raw.date = cursor.getStringOrNull(dateIndex)?.let(Date::from) raw.albumMusicBrainzId = cursor.getStringOrNull(albumMusicBrainzIdIndex) @@ -346,6 +349,7 @@ private class CacheDatabase(context: Context) : put(Columns.TRACK, rawSong.track) put(Columns.DISC, rawSong.disc) + put(Columns.SUBTITLE, rawSong.subtitle) put(Columns.DATE, rawSong.date?.toString()) put(Columns.ALBUM_MUSIC_BRAINZ_ID, rawSong.albumMusicBrainzId) @@ -414,6 +418,8 @@ private class CacheDatabase(context: Context) : const val TRACK = "track" /** @see Song.Raw.disc */ const val DISC = "disc" + /** @see Song.Raw.subtitle */ + const val SUBTITLE = "subtitle" /** @see Song.Raw.date */ const val DATE = "date" /** @see Song.Raw.albumMusicBrainzId */ @@ -442,7 +448,7 @@ private class CacheDatabase(context: Context) : companion object { private const val DB_NAME = "auxio_music_cache.db" - private const val DB_VERSION = 2 + private const val DB_VERSION = 3 private const val TABLE_RAW_SONGS = "raw_songs" @Volatile private var INSTANCE: CacheDatabase? = null diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt index cf7257194..91880466b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MetadataExtractor.kt @@ -178,15 +178,16 @@ class Task(context: Context, private val raw: Song.Raw) { */ private fun populateWithId3v2(textFrames: Map>) { // Song - textFrames["TXXX:musicbrainz release track id"]?.let { raw.musicBrainzId = it[0] } - textFrames["TIT2"]?.let { raw.name = it[0] } - textFrames["TSOT"]?.let { raw.sortName = it[0] } + textFrames["TXXX:musicbrainz release track id"]?.let { raw.musicBrainzId = it.first() } + textFrames["TIT2"]?.let { raw.name = it.first() } + textFrames["TSOT"]?.let { raw.sortName = it.first() } - // Track. Only parse out the track number and ignore the total tracks value. + // Track. textFrames["TRCK"]?.run { first().parseId3v2PositionField() }?.let { raw.track = it } - // Disc. Only parse out the disc number and ignore the total discs value. + // Disc and it's subtitle name. textFrames["TPOS"]?.run { first().parseId3v2PositionField() }?.let { raw.disc = it } + textFrames["TSST"]?.let { raw.subtitle = it.first() } // Dates are somewhat complicated, as not only did their semantics change from a flat year // value in ID3v2.3 to a full ISO-8601 date in ID3v2.4, but there are also a variety of @@ -204,9 +205,9 @@ class Task(context: Context, private val raw: Song.Raw) { ?.let { raw.date = it } // Album - textFrames["TXXX:musicbrainz album id"]?.let { raw.albumMusicBrainzId = it[0] } - textFrames["TALB"]?.let { raw.albumName = it[0] } - textFrames["TSOA"]?.let { raw.albumSortName = it[0] } + textFrames["TXXX:musicbrainz album id"]?.let { raw.albumMusicBrainzId = it.first() } + textFrames["TALB"]?.let { raw.albumName = it.first() } + textFrames["TSOA"]?.let { raw.albumSortName = it.first() } (textFrames["TXXX:musicbrainz album type"] ?: textFrames["GRP1"])?.let { raw.releaseTypes = it } @@ -244,19 +245,19 @@ class Task(context: Context, private val raw: Song.Raw) { ?: textFrames["TYER"]?.run { first().toIntOrNull() } ?: return null val tdat = textFrames["TDAT"] - return if (tdat != null && tdat[0].length == 4 && tdat[0].isDigitsOnly()) { + return if (tdat != null && tdat.first().length == 4 && tdat.first().isDigitsOnly()) { // TDAT frames consist of a 4-digit string where the first two digits are // the month and the last two digits are the day. - val mm = tdat[0].substring(0..1).toInt() - val dd = tdat[0].substring(2..3).toInt() + val mm = tdat.first().substring(0..1).toInt() + val dd = tdat.first().substring(2..3).toInt() val time = textFrames["TIME"] - if (time != null && time[0].length == 4 && time[0].isDigitsOnly()) { + if (time != null && time.first().length == 4 && time.first().isDigitsOnly()) { // TIME frames consist of a 4-digit string where the first two digits are // the hour and the last two digits are the minutes. No second value is // possible. - val hh = time[0].substring(0..1).toInt() - val mi = time[0].substring(2..3).toInt() + val hh = time.first().substring(0..1).toInt() + val mi = time.first().substring(2..3).toInt() // Able to return a full date. Date.from(year, mm, dd, hh, mi) } else { @@ -275,9 +276,9 @@ class Task(context: Context, private val raw: Song.Raw) { */ private fun populateWithVorbis(comments: Map>) { // Song - comments["musicbrainz_releasetrackid"]?.let { raw.musicBrainzId = it[0] } - comments["title"]?.let { raw.name = it[0] } - comments["titlesort"]?.let { raw.sortName = it[0] } + comments["musicbrainz_releasetrackid"]?.let { raw.musicBrainzId = it.first() } + comments["title"]?.let { raw.name = it.first() } + comments["titlesort"]?.let { raw.sortName = it.first() } // Track. parseVorbisPositionField( @@ -285,11 +286,12 @@ class Task(context: Context, private val raw: Song.Raw) { (comments["totaltracks"] ?: comments["tracktotal"] ?: comments["trackc"])?.first()) ?.let { raw.track = it } - // Disc. + // Disc and it's subtitle name. parseVorbisPositionField( comments["discnumber"]?.first(), (comments["totaldiscs"] ?: comments["disctotal"] ?: comments["discc"])?.first()) ?.let { raw.disc = it } + comments["discsubtitle"]?.let { raw.subtitle = it.first() } // Vorbis dates are less complicated, but there are still several types // Our hierarchy for dates is as such: @@ -303,9 +305,9 @@ class Task(context: Context, private val raw: Song.Raw) { ?.let { raw.date = it } // Album - comments["musicbrainz_albumid"]?.let { raw.albumMusicBrainzId = it[0] } - comments["album"]?.let { raw.albumName = it[0] } - comments["albumsort"]?.let { raw.albumSortName = it[0] } + comments["musicbrainz_albumid"]?.let { raw.albumMusicBrainzId = it.first() } + comments["album"]?.let { raw.albumName = it.first() } + comments["albumsort"]?.let { raw.albumSortName = it.first() } comments["releasetype"]?.let { raw.releaseTypes = it } // Artist diff --git a/app/src/main/java/org/oxycblt/auxio/music/library/Sort.kt b/app/src/main/java/org/oxycblt/auxio/music/library/Sort.kt index 2cda0e76f..f419d3747 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/library/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/library/Sort.kt @@ -24,6 +24,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.music.* import org.oxycblt.auxio.music.library.Sort.Mode import org.oxycblt.auxio.music.tags.Date +import org.oxycblt.auxio.music.tags.Disc /** * A sorting method. @@ -215,7 +216,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { override fun getSongComparator(isAscending: Boolean): Comparator = MultiComparator( compareByDynamic(isAscending, BasicComparator.ALBUM) { it.album }, - compareBy(NullableComparator.INT) { it.disc }, + compareBy(NullableComparator.DISC) { it.disc }, compareBy(NullableComparator.INT) { it.track }, compareBy(BasicComparator.SONG)) } @@ -236,7 +237,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { compareByDynamic(isAscending, ListComparator.ARTISTS) { it.artists }, compareByDescending(NullableComparator.DATE_RANGE) { it.album.dates }, compareByDescending(BasicComparator.ALBUM) { it.album }, - compareBy(NullableComparator.INT) { it.disc }, + compareBy(NullableComparator.DISC) { it.disc }, compareBy(NullableComparator.INT) { it.track }, compareBy(BasicComparator.SONG)) @@ -263,7 +264,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { MultiComparator( compareByDynamic(isAscending, NullableComparator.DATE_RANGE) { it.album.dates }, compareByDescending(BasicComparator.ALBUM) { it.album }, - compareBy(NullableComparator.INT) { it.disc }, + compareBy(NullableComparator.DISC) { it.disc }, compareBy(NullableComparator.INT) { it.track }, compareBy(BasicComparator.SONG)) @@ -342,7 +343,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { override fun getSongComparator(isAscending: Boolean): Comparator = MultiComparator( - compareByDynamic(isAscending, NullableComparator.INT) { it.disc }, + compareByDynamic(isAscending, NullableComparator.DISC) { it.disc }, compareBy(NullableComparator.INT) { it.track }, compareBy(BasicComparator.SONG)) } @@ -360,7 +361,7 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { override fun getSongComparator(isAscending: Boolean): Comparator = MultiComparator( - compareBy(NullableComparator.INT) { it.disc }, + compareBy(NullableComparator.DISC) { it.disc }, compareByDynamic(isAscending, NullableComparator.INT) { it.track }, compareBy(BasicComparator.SONG)) } @@ -545,6 +546,8 @@ data class Sort(val mode: Mode, val isAscending: Boolean) { val INT = NullableComparator() /** A re-usable instance configured for [Long]s. */ val LONG = NullableComparator() + /** A re-usable instance configured for [Disc]s */ + val DISC = NullableComparator() /** A re-usable instance configured for [Date.Range]s. */ val DATE_RANGE = NullableComparator() } diff --git a/app/src/main/java/org/oxycblt/auxio/music/tags/Disc.kt b/app/src/main/java/org/oxycblt/auxio/music/tags/Disc.kt new file mode 100644 index 000000000..f6f19f97c --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/tags/Disc.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Auxio Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.music.tags + +import org.oxycblt.auxio.list.Item + +/** + * A disc identifier for a song. + * @param number The disc number. + * @param name The name of the disc group, if any. Null if not present. + */ +class Disc(val number: Int, val name: String?) : Item, Comparable { + override fun hashCode() = number.hashCode() + override fun equals(other: Any?) = other is Disc && number == other.number + override fun compareTo(other: Disc) = number.compareTo(other.number) +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt index bd6900c51..b6e34615b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt @@ -300,7 +300,7 @@ class MediaSessionComponent(private val context: Context, private val listener: builder.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, it.toLong()) } song.disc?.let { - builder.putLong(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER, it.toLong()) + builder.putLong(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER, it.number.toLong()) } song.date?.let { builder.putString(MediaMetadataCompat.METADATA_KEY_DATE, it.toString()) } diff --git a/app/src/main/res/layout/item_disc_header.xml b/app/src/main/res/layout/item_disc_header.xml index 4aa39b3ef..2a291dbae 100644 --- a/app/src/main/res/layout/item_disc_header.xml +++ b/app/src/main/res/layout/item_disc_header.xml @@ -1,5 +1,5 @@ - + + + tools:visibility="gone" + app:layout_constraintStart_toEndOf="@+id/disc_icon" + app:layout_constraintTop_toBottomOf="@+id/disc_number" + tools:text="Part 1" /> - + diff --git a/app/src/test/java/org/oxycblt/auxio/music/tags/DiscTest.kt b/app/src/test/java/org/oxycblt/auxio/music/tags/DiscTest.kt new file mode 100644 index 000000000..eff1131d7 --- /dev/null +++ b/app/src/test/java/org/oxycblt/auxio/music/tags/DiscTest.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Auxio Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.music.tags + +import org.junit.Assert.assertTrue +import org.junit.Test + +class DiscTest { + @Test + fun disc_equals_correct() { + val a = Disc(1, "Part I") + val b = Disc(1, "Part I") + assertTrue(a == b) + assertTrue(a.hashCode() == b.hashCode()) + } + + @Test + fun disc_equals_inconsistentNames() { + val a = Disc(1, "Part I") + val b = Disc(1, null) + assertTrue(a == b) + assertTrue(a.hashCode() == b.hashCode()) + } +}