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 81282e354..ac009935e 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.ReleaseType import org.oxycblt.auxio.playback.PlaybackSettings import org.oxycblt.auxio.util.* @@ -344,21 +345,21 @@ class DetailViewModel(application: Application) : val byReleaseGroup = albums.groupBy { - // Remap the complicated Album.Type data structure into an easier + // Remap the complicated ReleaseType data structure into an easier // "AlbumGrouping" enum that will automatically group and sort // the artist's albums. - when (it.type.refinement) { - Album.Type.Refinement.LIVE -> AlbumGrouping.LIVE - Album.Type.Refinement.REMIX -> AlbumGrouping.REMIXES + when (it.releaseType.refinement) { + ReleaseType.Refinement.LIVE -> AlbumGrouping.LIVE + ReleaseType.Refinement.REMIX -> AlbumGrouping.REMIXES null -> - when (it.type) { - is Album.Type.Album -> AlbumGrouping.ALBUMS - is Album.Type.EP -> AlbumGrouping.EPS - is Album.Type.Single -> AlbumGrouping.SINGLES - is Album.Type.Compilation -> AlbumGrouping.COMPILATIONS - is Album.Type.Soundtrack -> AlbumGrouping.SOUNDTRACKS - is Album.Type.Mix -> AlbumGrouping.MIXES - is Album.Type.Mixtape -> AlbumGrouping.MIXTAPES + when (it.releaseType) { + is ReleaseType.Album -> AlbumGrouping.ALBUMS + is ReleaseType.EP -> AlbumGrouping.EPS + is ReleaseType.Single -> AlbumGrouping.SINGLES + is ReleaseType.Compilation -> AlbumGrouping.COMPILATIONS + is ReleaseType.Soundtrack -> AlbumGrouping.SOUNDTRACKS + is ReleaseType.Mix -> AlbumGrouping.MIXES + is ReleaseType.Mixtape -> AlbumGrouping.MIXTAPES } } } @@ -392,7 +393,7 @@ class DetailViewModel(application: Application) : } /** - * A simpler mapping of [Album.Type] used for grouping and sorting songs. + * A simpler mapping of [ReleaseType] used for grouping and sorting songs. * @param headerTitleRes The title string resource to use for a header created out of an * instance of this enum. */ 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 56a86cc6f..d6855c09f 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 @@ -126,7 +126,7 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite binding.detailCover.bind(album) // The type text depends on the release type (Album, EP, Single, etc.) - binding.detailType.text = binding.context.getString(album.type.stringRes) + binding.detailType.text = binding.context.getString(album.releaseType.stringRes) binding.detailName.text = album.resolveName(binding.context) @@ -173,7 +173,7 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite oldItem.dates == newItem.dates && oldItem.songs.size == newItem.songs.size && oldItem.durationMs == newItem.durationMs && - oldItem.type == newItem.type + oldItem.releaseType == newItem.releaseType } } } diff --git a/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt index 3aaac0609..75e570cf8 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/recycler/ViewHolders.kt @@ -122,7 +122,7 @@ class AlbumViewHolder private constructor(private val binding: ItemParentBinding override fun areContentsTheSame(oldItem: Album, newItem: Album) = oldItem.rawName == newItem.rawName && oldItem.areArtistContentsTheSame(newItem) && - oldItem.type == newItem.type + oldItem.releaseType == newItem.releaseType } } } 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 481a2116b..e4de7b1a2 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -34,6 +34,8 @@ import org.oxycblt.auxio.music.library.Sort 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.ReleaseType import org.oxycblt.auxio.util.nonZeroOrNull import org.oxycblt.auxio.util.unlikelyToBeNull @@ -463,7 +465,7 @@ class Song constructor(raw: Raw, musicSettings: MusicSettings) : Music() { musicBrainzId = raw.albumMusicBrainzId?.toUuidOrNull(), name = requireNotNull(raw.albumName) { "Invalid raw: No album name" }, sortName = raw.albumSortName, - type = Album.Type.parse(raw.albumTypes.parseMultiValue(musicSettings)), + releaseType = ReleaseType.parse(raw.releaseTypes.parseMultiValue(musicSettings)), rawArtists = rawAlbumArtists.ifEmpty { rawArtists }.ifEmpty { listOf(Artist.Raw(null, null)) }) @@ -582,8 +584,8 @@ class Song constructor(raw: Raw, musicSettings: MusicSettings) : Music() { var albumName: String? = null, /** @see Album.Raw.sortName */ var albumSortName: String? = null, - /** @see Album.Raw.type */ - var albumTypes: List = listOf(), + /** @see Album.Raw.releaseType */ + var releaseTypes: List = listOf(), /** @see Artist.Raw.musicBrainzId */ var artistMusicBrainzIds: List = listOf(), /** @see Artist.Raw.name */ @@ -629,10 +631,10 @@ class Album constructor(raw: Raw, override val songs: List) : MusicParent( val dates = Date.Range.from(songs.mapNotNull { it.date }) /** - * The [Type] of this album, signifying the type of release it actually is. Defaults to - * [Type.Album]. + * The [ReleaseType] of this album, signifying the type of release it actually is. Defaults to + * [ReleaseType.Album]. */ - val type = raw.type ?: Type.Album(null) + val releaseType = raw.releaseType ?: ReleaseType.Album(null) /** * The URI to a MediaStore-provided album cover. These images will be fast to load, but at the * cost of image quality. @@ -727,201 +729,6 @@ class Album constructor(raw: Raw, override val songs: List) : MusicParent( } } - /** - * The type of release an [Album] is considered. This includes EPs, Singles, Compilations, etc. - * - * This class is derived from the MusicBrainz Release Group Type specification. It can be found - * at: https://musicbrainz.org/doc/Release_Group/Type - * @author Alexander Capehart (OxygenCobalt) - */ - sealed class Type { - /** - * A specification of what kind of performance this release is. If null, the release is - * considered "Plain". - */ - abstract val refinement: Refinement? - - /** The string resource corresponding to the name of this release type to show in the UI. */ - abstract val stringRes: Int - - /** - * A plain album. - * @param refinement A specification of what kind of performance this release is. If null, - * the release is considered "Plain". - */ - data class Album(override val refinement: Refinement?) : Type() { - override val stringRes: Int - get() = - when (refinement) { - null -> R.string.lbl_album - // If present, include the refinement in the name of this release type. - Refinement.LIVE -> R.string.lbl_album_live - Refinement.REMIX -> R.string.lbl_album_remix - } - } - - /** - * A "Extended Play", or EP. Usually a smaller release consisting of 4-5 songs. - * @param refinement A specification of what kind of performance this release is. If null, - * the release is considered "Plain". - */ - data class EP(override val refinement: Refinement?) : Type() { - override val stringRes: Int - get() = - when (refinement) { - null -> R.string.lbl_ep - // If present, include the refinement in the name of this release type. - Refinement.LIVE -> R.string.lbl_ep_live - Refinement.REMIX -> R.string.lbl_ep_remix - } - } - - /** - * A single. Usually a release consisting of 1-2 songs. - * @param refinement A specification of what kind of performance this release is. If null, - * the release is considered "Plain". - */ - data class Single(override val refinement: Refinement?) : Type() { - override val stringRes: Int - get() = - when (refinement) { - null -> R.string.lbl_single - // If present, include the refinement in the name of this release type. - Refinement.LIVE -> R.string.lbl_single_live - Refinement.REMIX -> R.string.lbl_single_remix - } - } - - /** - * A compilation. Usually consists of many songs from a variety of artists. - * @param refinement A specification of what kind of performance this release is. If null, - * the release is considered "Plain". - */ - data class Compilation(override val refinement: Refinement?) : Type() { - override val stringRes: Int - get() = - when (refinement) { - null -> R.string.lbl_compilation - // If present, include the refinement in the name of this release type. - Refinement.LIVE -> R.string.lbl_compilation_live - Refinement.REMIX -> R.string.lbl_compilation_remix - } - } - - /** - * A soundtrack. Similar to a [Compilation], but created for a specific piece of (usually - * visual) media. - */ - object Soundtrack : Type() { - override val refinement: Refinement? - get() = null - - override val stringRes: Int - get() = R.string.lbl_soundtrack - } - - /** - * A (DJ) Mix. These are usually one large track consisting of the artist playing several - * sub-tracks with smooth transitions between them. - */ - object Mix : Type() { - override val refinement: Refinement? - get() = null - - override val stringRes: Int - get() = R.string.lbl_mix - } - - /** - * A Mix-tape. These are usually [EP]-sized releases of music made to promote an [Artist] or - * a future release. - */ - object Mixtape : Type() { - override val refinement: Refinement? - get() = null - - override val stringRes: Int - get() = R.string.lbl_mixtape - } - - /** A specification of what kind of performance a particular release is. */ - enum class Refinement { - /** A release consisting of a live performance */ - LIVE, - - /** A release consisting of another [Artist]s remix of a prior performance. */ - REMIX - } - - companion object { - /** - * Parse a [Type] from a string formatted with the MusicBrainz Release Group Type - * specification. - * @param types A list of values consisting of valid release type values. - * @return A [Type] consisting of the given types, or null if the types were not valid. - */ - fun parse(types: List): Type? { - val primary = types.getOrNull(0) ?: return null - return when { - // Primary types should be the first types in the sequence. - primary.equals("album", true) -> types.parseSecondaryTypes(1) { Album(it) } - primary.equals("ep", true) -> types.parseSecondaryTypes(1) { EP(it) } - primary.equals("single", true) -> types.parseSecondaryTypes(1) { Single(it) } - // The spec makes no mention of whether primary types are a pre-requisite for - // secondary types, so we assume that it's not and map oprhan secondary types - // to Album release types. - else -> types.parseSecondaryTypes(0) { Album(it) } - } - } - - /** - * Parse "secondary" types (i.e not [Album], [EP], or [Single]) from a string formatted - * with the MusicBrainz Release Group Type specification. - * @param index The index of the release type to parse. - * @param convertRefinement Code to convert a [Refinement] into a [Type] corresponding - * to the callee's context. This is used in order to handle secondary times that are - * actually [Refinement]s. - * @return A [Type] corresponding to the secondary type found at that index. - */ - private inline fun List.parseSecondaryTypes( - index: Int, - convertRefinement: (Refinement?) -> Type - ): Type { - val secondary = getOrNull(index) - return if (secondary.equals("compilation", true)) { - // Secondary type is a compilation, actually parse the third type - // and put that into a compilation if needed. - parseSecondaryTypeImpl(getOrNull(index + 1)) { Compilation(it) } - } else { - // Secondary type is a plain value, use the original values given. - parseSecondaryTypeImpl(secondary, convertRefinement) - } - } - - /** - * Parse "secondary" types (i.e not [Album], [EP], [Single]) that do not correspond to - * any child values. - * @param type The release type value to parse. - * @param convertRefinement Code to convert a [Refinement] into a [Type] corresponding - * to the callee's context. This is used in order to handle secondary times that are - * actually [Refinement]s. - */ - private inline fun parseSecondaryTypeImpl( - type: String?, - convertRefinement: (Refinement?) -> Type - ) = - when { - // Parse all the types that have no children - type.equals("soundtrack", true) -> Soundtrack - type.equals("mixtape/street", true) -> Mixtape - type.equals("dj-mix", true) -> Mix - type.equals("live", true) -> convertRefinement(Refinement.LIVE) - type.equals("remix", true) -> convertRefinement(Refinement.REMIX) - else -> convertRefinement(null) - } - } - } - /** * Raw information about an [Album] obtained from the component [Song] instances. **This is only * meant for use within the music package.** @@ -938,8 +745,8 @@ class Album constructor(raw: Raw, override val songs: List) : MusicParent( val name: String, /** @see Music.rawSortName */ val sortName: String?, - /** @see Album.type */ - val type: Type?, + /** @see Album.releaseType */ + val releaseType: ReleaseType?, /** @see Artist.Raw.name */ val rawArtists: List ) { 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 10dc6ed72..99115f755 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 @@ -23,7 +23,7 @@ import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import androidx.core.database.getIntOrNull import androidx.core.database.getStringOrNull -import org.oxycblt.auxio.music.Date +import org.oxycblt.auxio.music.tags.Date import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.parsing.correctWhitespace import org.oxycblt.auxio.music.parsing.splitEscaped @@ -142,7 +142,7 @@ class ReadWriteCacheExtractor(private val context: Context) : WriteOnlyCacheExtr rawSong.albumMusicBrainzId = cachedRawSong.albumMusicBrainzId rawSong.albumName = cachedRawSong.albumName rawSong.albumSortName = cachedRawSong.albumSortName - rawSong.albumTypes = cachedRawSong.albumTypes + rawSong.releaseTypes = cachedRawSong.releaseTypes rawSong.artistMusicBrainzIds = cachedRawSong.artistMusicBrainzIds rawSong.artistNames = cachedRawSong.artistNames @@ -190,7 +190,7 @@ private class CacheDatabase(context: Context) : append("${Columns.ALBUM_MUSIC_BRAINZ_ID} STRING,") append("${Columns.ALBUM_NAME} STRING NOT NULL,") append("${Columns.ALBUM_SORT_NAME} STRING,") - append("${Columns.ALBUM_TYPES} STRING,") + append("${Columns.RELEASE_TYPES} STRING,") append("${Columns.ARTIST_MUSIC_BRAINZ_IDS} STRING,") append("${Columns.ARTIST_NAMES} STRING,") append("${Columns.ARTIST_SORT_NAMES} STRING,") @@ -249,7 +249,7 @@ private class CacheDatabase(context: Context) : cursor.getColumnIndexOrThrow(Columns.ALBUM_MUSIC_BRAINZ_ID) val albumNameIndex = cursor.getColumnIndexOrThrow(Columns.ALBUM_NAME) val albumSortNameIndex = cursor.getColumnIndexOrThrow(Columns.ALBUM_SORT_NAME) - val albumTypesIndex = cursor.getColumnIndexOrThrow(Columns.ALBUM_TYPES) + val releaseTypesIndex = cursor.getColumnIndexOrThrow(Columns.RELEASE_TYPES) val artistMusicBrainzIdsIndex = cursor.getColumnIndexOrThrow(Columns.ARTIST_MUSIC_BRAINZ_IDS) @@ -286,8 +286,8 @@ private class CacheDatabase(context: Context) : raw.albumMusicBrainzId = cursor.getStringOrNull(albumMusicBrainzIdIndex) raw.albumName = cursor.getString(albumNameIndex) raw.albumSortName = cursor.getStringOrNull(albumSortNameIndex) - cursor.getStringOrNull(albumTypesIndex)?.let { - raw.albumTypes = it.parseSQLMultiValue() + cursor.getStringOrNull(releaseTypesIndex)?.let { + raw.releaseTypes = it.parseSQLMultiValue() } cursor.getStringOrNull(artistMusicBrainzIdsIndex)?.let { @@ -351,7 +351,7 @@ private class CacheDatabase(context: Context) : put(Columns.ALBUM_MUSIC_BRAINZ_ID, rawSong.albumMusicBrainzId) put(Columns.ALBUM_NAME, rawSong.albumName) put(Columns.ALBUM_SORT_NAME, rawSong.albumSortName) - put(Columns.ALBUM_TYPES, rawSong.albumTypes.toSQLMultiValue()) + put(Columns.RELEASE_TYPES, rawSong.releaseTypes.toSQLMultiValue()) put(Columns.ARTIST_MUSIC_BRAINZ_IDS, rawSong.artistMusicBrainzIds.toSQLMultiValue()) put(Columns.ARTIST_NAMES, rawSong.artistNames.toSQLMultiValue()) @@ -422,8 +422,8 @@ private class CacheDatabase(context: Context) : const val ALBUM_NAME = "album" /** @see Song.Raw.albumSortName */ const val ALBUM_SORT_NAME = "album_sort" - /** @see Song.Raw.albumTypes */ - const val ALBUM_TYPES = "album_types" + /** @see Song.Raw.releaseTypes */ + const val RELEASE_TYPES = "album_types" /** @see Song.Raw.artistMusicBrainzIds */ const val ARTIST_MUSIC_BRAINZ_IDS = "artists_mbid" /** @see Song.Raw.artistNames */ diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt index 62b983672..296c0595c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/MediaStoreExtractor.kt @@ -27,7 +27,7 @@ import androidx.annotation.RequiresApi import androidx.core.database.getIntOrNull import androidx.core.database.getStringOrNull import java.io.File -import org.oxycblt.auxio.music.Date +import org.oxycblt.auxio.music.tags.Date import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.parsing.parseId3v2Position 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 f4a203778..c86267206 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 @@ -22,7 +22,7 @@ import androidx.core.text.isDigitsOnly import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MetadataRetriever import kotlinx.coroutines.flow.flow -import org.oxycblt.auxio.music.Date +import org.oxycblt.auxio.music.tags.Date import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.parsing.parseId3v2Position import org.oxycblt.auxio.music.storage.toAudioUri @@ -208,7 +208,7 @@ class Task(context: Context, private val raw: Song.Raw) { textFrames["TALB"]?.let { raw.albumName = it[0] } textFrames["TSOA"]?.let { raw.albumSortName = it[0] } (textFrames["TXXX:musicbrainz album type"] ?: textFrames["GRP1"])?.let { - raw.albumTypes = it + raw.releaseTypes = it } // Artist @@ -300,7 +300,7 @@ class Task(context: Context, private val raw: Song.Raw) { comments["musicbrainz_albumid"]?.let { raw.albumMusicBrainzId = it[0] } comments["album"]?.let { raw.albumName = it[0] } comments["albumsort"]?.let { raw.albumSortName = it[0] } - comments["releasetype"]?.let { raw.albumTypes = it } + comments["releasetype"]?.let { raw.releaseTypes = it } // Artist comments["musicbrainz_artistid"]?.let { raw.artistMusicBrainzIds = it } 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 01d57a57d..a126f3879 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 @@ -23,6 +23,7 @@ import org.oxycblt.auxio.IntegerTable 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 /** * A sorting method. diff --git a/app/src/main/java/org/oxycblt/auxio/music/Date.kt b/app/src/main/java/org/oxycblt/auxio/music/tags/Date.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/Date.kt rename to app/src/main/java/org/oxycblt/auxio/music/tags/Date.kt index ad815a779..1f1340de8 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Date.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/tags/Date.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music +package org.oxycblt.auxio.music.tags import android.content.Context import java.text.ParseException @@ -235,7 +235,7 @@ class Date private constructor(private val tokens: List) : Comparable val tokens = // Match the input with the timestamp regex. If there is no match, see if we can // fall back to some kind of year value. - (ISO8601_REGEX.matchEntire(timestamp) ?: return timestamp.toIntOrNull()?.let(::from)) + (ISO8601_REGEX.matchEntire(timestamp) ?: return timestamp.toIntOrNull()?.let(Companion::from)) .groupValues // Filter to the specific tokens we want and convert them to integer tokens. .mapIndexedNotNull { index, s -> if (index % 2 != 0) s.toIntOrNull() else null } diff --git a/app/src/main/java/org/oxycblt/auxio/music/tags/ReleaseType.kt b/app/src/main/java/org/oxycblt/auxio/music/tags/ReleaseType.kt new file mode 100644 index 000000000..fd9567154 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/music/tags/ReleaseType.kt @@ -0,0 +1,198 @@ +package org.oxycblt.auxio.music.tags + +import org.oxycblt.auxio.R + +/** + * The type of release an [Album] is considered. This includes EPs, Singles, Compilations, etc. + * + * This class is derived from the MusicBrainz Release Group Type specification. It can be found + * at: https://musicbrainz.org/doc/Release_Group/Type + * @author Alexander Capehart (OxygenCobalt) + */ +sealed class ReleaseType { + /** + * A specification of what kind of performance this release is. If null, the release is + * considered "Plain". + */ + abstract val refinement: Refinement? + + /** The string resource corresponding to the name of this release type to show in the UI. */ + abstract val stringRes: Int + + /** + * A plain album. + * @param refinement A specification of what kind of performance this release is. If null, + * the release is considered "Plain". + */ + data class Album(override val refinement: Refinement?) : ReleaseType() { + override val stringRes: Int + get() = + when (refinement) { + null -> R.string.lbl_album + // If present, include the refinement in the name of this release type. + Refinement.LIVE -> R.string.lbl_album_live + Refinement.REMIX -> R.string.lbl_album_remix + } + } + + /** + * A "Extended Play", or EP. Usually a smaller release consisting of 4-5 songs. + * @param refinement A specification of what kind of performance this release is. If null, + * the release is considered "Plain". + */ + data class EP(override val refinement: Refinement?) : ReleaseType() { + override val stringRes: Int + get() = + when (refinement) { + null -> R.string.lbl_ep + // If present, include the refinement in the name of this release type. + Refinement.LIVE -> R.string.lbl_ep_live + Refinement.REMIX -> R.string.lbl_ep_remix + } + } + + /** + * A single. Usually a release consisting of 1-2 songs. + * @param refinement A specification of what kind of performance this release is. If null, + * the release is considered "Plain". + */ + data class Single(override val refinement: Refinement?) : ReleaseType() { + override val stringRes: Int + get() = + when (refinement) { + null -> R.string.lbl_single + // If present, include the refinement in the name of this release type. + Refinement.LIVE -> R.string.lbl_single_live + Refinement.REMIX -> R.string.lbl_single_remix + } + } + + /** + * A compilation. Usually consists of many songs from a variety of artists. + * @param refinement A specification of what kind of performance this release is. If null, + * the release is considered "Plain". + */ + data class Compilation(override val refinement: Refinement?) : ReleaseType() { + override val stringRes: Int + get() = + when (refinement) { + null -> R.string.lbl_compilation + // If present, include the refinement in the name of this release type. + Refinement.LIVE -> R.string.lbl_compilation_live + Refinement.REMIX -> R.string.lbl_compilation_remix + } + } + + /** + * A soundtrack. Similar to a [Compilation], but created for a specific piece of (usually + * visual) media. + */ + object Soundtrack : ReleaseType() { + override val refinement: Refinement? + get() = null + + override val stringRes: Int + get() = R.string.lbl_soundtrack + } + + /** + * A (DJ) Mix. These are usually one large track consisting of the artist playing several + * sub-tracks with smooth transitions between them. + */ + object Mix : ReleaseType() { + override val refinement: Refinement? + get() = null + + override val stringRes: Int + get() = R.string.lbl_mix + } + + /** + * A Mix-tape. These are usually [EP]-sized releases of music made to promote an [Artist] or + * a future release. + */ + object Mixtape : ReleaseType() { + override val refinement: Refinement? + get() = null + + override val stringRes: Int + get() = R.string.lbl_mixtape + } + + /** A specification of what kind of performance a particular release is. */ + enum class Refinement { + /** A release consisting of a live performance */ + LIVE, + + /** A release consisting of another [Artist]s remix of a prior performance. */ + REMIX + } + + companion object { + /** + * Parse a [ReleaseType] from a string formatted with the MusicBrainz Release Group Type + * specification. + * @param types A list of values consisting of valid release type values. + * @return A [ReleaseType] consisting of the given types, or null if the types were not valid. + */ + fun parse(types: List): ReleaseType? { + val primary = types.getOrNull(0) ?: return null + return when { + // Primary types should be the first types in the sequence. + primary.equals("album", true) -> types.parseSecondaryTypes(1) { Album(it) } + primary.equals("ep", true) -> types.parseSecondaryTypes(1) { EP(it) } + primary.equals("single", true) -> types.parseSecondaryTypes(1) { Single(it) } + // The spec makes no mention of whether primary types are a pre-requisite for + // secondary types, so we assume that it's not and map oprhan secondary types + // to Album release types. + else -> types.parseSecondaryTypes(0) { Album(it) } + } + } + + /** + * Parse "secondary" types (i.e not [Album], [EP], or [Single]) from a string formatted + * with the MusicBrainz Release Group Type specification. + * @param index The index of the release type to parse. + * @param convertRefinement Code to convert a [Refinement] into a [ReleaseType] corresponding + * to the callee's context. This is used in order to handle secondary times that are + * actually [Refinement]s. + * @return A [ReleaseType] corresponding to the secondary type found at that index. + */ + private inline fun List.parseSecondaryTypes( + index: Int, + convertRefinement: (Refinement?) -> ReleaseType + ): ReleaseType { + val secondary = getOrNull(index) + return if (secondary.equals("compilation", true)) { + // Secondary type is a compilation, actually parse the third type + // and put that into a compilation if needed. + parseSecondaryTypeImpl(getOrNull(index + 1)) { Compilation(it) } + } else { + // Secondary type is a plain value, use the original values given. + parseSecondaryTypeImpl(secondary, convertRefinement) + } + } + + /** + * Parse "secondary" types (i.e not [Album], [EP], [Single]) that do not correspond to + * any child values. + * @param type The release type value to parse. + * @param convertRefinement Code to convert a [Refinement] into a [ReleaseType] corresponding + * to the callee's context. This is used in order to handle secondary times that are + * actually [Refinement]s. + */ + private inline fun parseSecondaryTypeImpl( + type: String?, + convertRefinement: (Refinement?) -> ReleaseType + ) = + when { + // Parse all the types that have no children + type.equals("soundtrack", true) -> Soundtrack + type.equals("mixtape/street", true) -> Mixtape + type.equals("dj-mix", true) -> Mix + type.equals("live", true) -> convertRefinement(Refinement.LIVE) + type.equals("remix", true) -> convertRefinement(Refinement.REMIX) + else -> convertRefinement(null) + } + } +} \ No newline at end of file diff --git a/app/src/test/java/org/oxycblt/auxio/music/AlbumTypeTest.kt b/app/src/test/java/org/oxycblt/auxio/music/AlbumTypeTest.kt deleted file mode 100644 index ea4581f54..000000000 --- a/app/src/test/java/org/oxycblt/auxio/music/AlbumTypeTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 - -import org.junit.Assert.assertEquals -import org.junit.Test - -class AlbumTypeTest { - @Test - fun albumType_parse_primary() { - assertEquals(Album.Type.Album(null), Album.Type.parse(listOf("album"))) - assertEquals(Album.Type.EP(null), Album.Type.parse(listOf("ep"))) - assertEquals(Album.Type.Single(null), Album.Type.parse(listOf("single"))) - } - - @Test - fun albumType_parse_secondary() { - assertEquals(Album.Type.Compilation(null), Album.Type.parse(listOf("album", "compilation"))) - assertEquals(Album.Type.Soundtrack, Album.Type.parse(listOf("album", "soundtrack"))) - assertEquals(Album.Type.Mix, Album.Type.parse(listOf("album", "dj-mix"))) - assertEquals(Album.Type.Mixtape, Album.Type.parse(listOf("album", "mixtape/street"))) - } - - @Test - fun albumType_parse_modifiers() { - assertEquals( - Album.Type.Album(Album.Type.Refinement.LIVE), Album.Type.parse(listOf("album", "live"))) - assertEquals( - Album.Type.Album(Album.Type.Refinement.REMIX), - Album.Type.parse(listOf("album", "remix"))) - assertEquals( - Album.Type.EP(Album.Type.Refinement.LIVE), Album.Type.parse(listOf("ep", "live"))) - assertEquals( - Album.Type.EP(Album.Type.Refinement.REMIX), Album.Type.parse(listOf("ep", "remix"))) - assertEquals( - Album.Type.Single(Album.Type.Refinement.LIVE), - Album.Type.parse(listOf("single", "live"))) - assertEquals( - Album.Type.Single(Album.Type.Refinement.REMIX), - Album.Type.parse(listOf("single", "remix"))) - } - - @Test - fun albumType_parse_secondaryModifiers() { - assertEquals( - Album.Type.Compilation(Album.Type.Refinement.LIVE), - Album.Type.parse(listOf("album", "compilation", "live"))) - assertEquals( - Album.Type.Compilation(Album.Type.Refinement.REMIX), - Album.Type.parse(listOf("album", "compilation", "remix"))) - } - - @Test - fun albumType_parse_orphanedSecondary() { - assertEquals(Album.Type.Compilation(null), Album.Type.parse(listOf("compilation"))) - assertEquals(Album.Type.Soundtrack, Album.Type.parse(listOf("soundtrack"))) - assertEquals(Album.Type.Mix, Album.Type.parse(listOf("dj-mix"))) - assertEquals(Album.Type.Mixtape, Album.Type.parse(listOf("mixtape/street"))) - } - - @Test - fun albumType_parse_orphanedModifier() { - assertEquals(Album.Type.Album(Album.Type.Refinement.LIVE), Album.Type.parse(listOf("live"))) - assertEquals( - Album.Type.Album(Album.Type.Refinement.REMIX), Album.Type.parse(listOf("remix"))) - } -} diff --git a/app/src/test/java/org/oxycblt/auxio/music/library/LibraryTest.kt b/app/src/test/java/org/oxycblt/auxio/music/library/LibraryTest.kt new file mode 100644 index 000000000..8d4ab0f15 --- /dev/null +++ b/app/src/test/java/org/oxycblt/auxio/music/library/LibraryTest.kt @@ -0,0 +1,14 @@ +package org.oxycblt.auxio.music.library + +import org.oxycblt.auxio.music.Song + +class LibraryTest { + + companion object { + val LIBRARY = listOf( + Song.Raw( + + ) + ) + } +} \ No newline at end of file diff --git a/app/src/test/java/org/oxycblt/auxio/music/DateTest.kt b/app/src/test/java/org/oxycblt/auxio/music/tags/DateTest.kt similarity index 99% rename from app/src/test/java/org/oxycblt/auxio/music/DateTest.kt rename to app/src/test/java/org/oxycblt/auxio/music/tags/DateTest.kt index bd21969fc..658b37e97 100644 --- a/app/src/test/java/org/oxycblt/auxio/music/DateTest.kt +++ b/app/src/test/java/org/oxycblt/auxio/music/tags/DateTest.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music +package org.oxycblt.auxio.music.tags import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue diff --git a/app/src/test/java/org/oxycblt/auxio/music/tags/ReleaseTypeTest.kt b/app/src/test/java/org/oxycblt/auxio/music/tags/ReleaseTypeTest.kt new file mode 100644 index 000000000..63999edf3 --- /dev/null +++ b/app/src/test/java/org/oxycblt/auxio/music/tags/ReleaseTypeTest.kt @@ -0,0 +1,82 @@ +/* + * 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.assertEquals +import org.junit.Test + +class ReleaseTypeTest { + @Test + fun releaseType_parse_primary() { + assertEquals(ReleaseType.Album(null), ReleaseType.parse(listOf("album"))) + assertEquals(ReleaseType.EP(null), ReleaseType.parse(listOf("ep"))) + assertEquals(ReleaseType.Single(null), ReleaseType.parse(listOf("single"))) + } + + @Test + fun releaseType_parse_secondary() { + assertEquals(ReleaseType.Compilation(null), ReleaseType.parse(listOf("album", "compilation"))) + assertEquals(ReleaseType.Soundtrack, ReleaseType.parse(listOf("album", "soundtrack"))) + assertEquals(ReleaseType.Mix, ReleaseType.parse(listOf("album", "dj-mix"))) + assertEquals(ReleaseType.Mixtape, ReleaseType.parse(listOf("album", "mixtape/street"))) + } + + @Test + fun releaseType_parse_modifiers() { + assertEquals( + ReleaseType.Album(ReleaseType.Refinement.LIVE), ReleaseType.parse(listOf("album", "live"))) + assertEquals( + ReleaseType.Album(ReleaseType.Refinement.REMIX), + ReleaseType.parse(listOf("album", "remix"))) + assertEquals( + ReleaseType.EP(ReleaseType.Refinement.LIVE), ReleaseType.parse(listOf("ep", "live"))) + assertEquals( + ReleaseType.EP(ReleaseType.Refinement.REMIX), ReleaseType.parse(listOf("ep", "remix"))) + assertEquals( + ReleaseType.Single(ReleaseType.Refinement.LIVE), + ReleaseType.parse(listOf("single", "live"))) + assertEquals( + ReleaseType.Single(ReleaseType.Refinement.REMIX), + ReleaseType.parse(listOf("single", "remix"))) + } + + @Test + fun releaseType_parse_secondaryModifiers() { + assertEquals( + ReleaseType.Compilation(ReleaseType.Refinement.LIVE), + ReleaseType.parse(listOf("album", "compilation", "live"))) + assertEquals( + ReleaseType.Compilation(ReleaseType.Refinement.REMIX), + ReleaseType.parse(listOf("album", "compilation", "remix"))) + } + + @Test + fun releaseType_parse_orphanedSecondary() { + assertEquals(ReleaseType.Compilation(null), ReleaseType.parse(listOf("compilation"))) + assertEquals(ReleaseType.Soundtrack, ReleaseType.parse(listOf("soundtrack"))) + assertEquals(ReleaseType.Mix, ReleaseType.parse(listOf("dj-mix"))) + assertEquals(ReleaseType.Mixtape, ReleaseType.parse(listOf("mixtape/street"))) + } + + @Test + fun releaseType_parse_orphanedModifier() { + assertEquals(ReleaseType.Album(ReleaseType.Refinement.LIVE), ReleaseType.parse(listOf("live"))) + assertEquals( + ReleaseType.Album(ReleaseType.Refinement.REMIX), ReleaseType.parse(listOf("remix"))) + } +}