From ba94d4fa21235678800e370aad088fea98379ea4 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Thu, 25 May 2023 12:54:23 -0600 Subject: [PATCH] detail: group implicit albums in "appears on" Group albums implicitly linked to an artist via the "artist" tag into their own section called "Appears on". This makes Auxio's artist model a bit more apparent to users. Resolves #411. --- CHANGELOG.md | 9 +++ .../oxycblt/auxio/detail/DetailViewModel.kt | 57 +++++++++++-------- .../detail/list/AlbumDetailListAdapter.kt | 1 + .../org/oxycblt/auxio/home/HomeViewModel.kt | 2 +- .../java/org/oxycblt/auxio/music/Music.kt | 18 +++--- .../auxio/music/device/DeviceMusicImpl.kt | 19 ++++--- app/src/main/res/values/strings.xml | 6 +- 7 files changed, 69 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b90dedf..9c3473dc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ #### What's New - Added ability to share a track +#### What's Improved +- Tracks with no disc number now default to "No Disc" instead of "Disc 1" +- Albums implicitly linked only via "artist" tags are now placed in a special +"appears on" section in the artist view + +#### What's Fixed +- Prevented options such as "Add to queue" from being selected on empty +artists and playlists + ## 3.1.0 #### What's New 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 26c64b2b1..67ebf89f1 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -428,32 +428,42 @@ constructor( private fun refreshArtistList(artist: Artist, replace: Boolean = false) { logD("Refreshing artist list") val list = mutableListOf() - val albums = Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING).albums(artist.albums) - val byReleaseGroup = - albums.groupBy { - // Remap the complicated ReleaseType data structure into an easier - // "AlbumGrouping" enum that will automatically group and sort - // the artist's albums. - when (it.releaseType.refinement) { - ReleaseType.Refinement.LIVE -> AlbumGrouping.LIVE - ReleaseType.Refinement.REMIX -> AlbumGrouping.REMIXES - null -> - 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 - } + val grouping = + Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING) + .albums(artist.explicitAlbums) + .groupByTo(sortedMapOf()) { + // Remap the complicated ReleaseType data structure into an easier + // "AlbumGrouping" enum that will automatically group and sort + // the artist's albums. + when (it.releaseType.refinement) { + ReleaseType.Refinement.LIVE -> AlbumGrouping.LIVE + ReleaseType.Refinement.REMIX -> AlbumGrouping.REMIXES + null -> + 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.DJMIXES + is ReleaseType.Mixtape -> AlbumGrouping.MIXTAPES + } + } } - } - logD("Release groups for this artist: ${byReleaseGroup.keys}") + if (artist.implicitAlbums.isNotEmpty()) { + // groupByTo normally returns a mapping to a MutableList mapping. Since MutableList + // inherits list, we can cast upwards and save a copy by directly inserting the + // implicit album list into the mapping. + @Suppress("UNCHECKED_CAST") + (grouping as MutableMap>)[AlbumGrouping.APPEARANCES] = + Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING).albums(artist.implicitAlbums) + } - for (entry in byReleaseGroup.entries.sortedBy { it.key }) { + logD("Release groups for this artist: ${grouping.keys}") + + for (entry in grouping.entries) { val header = BasicHeader(entry.key.headerTitleRes) list.add(Divider(header)) list.add(header) @@ -533,8 +543,9 @@ constructor( SINGLES(R.string.lbl_singles), COMPILATIONS(R.string.lbl_compilations), SOUNDTRACKS(R.string.lbl_soundtracks), - MIXES(R.string.lbl_mixes), + DJMIXES(R.string.lbl_mixes), MIXTAPES(R.string.lbl_mixtapes), + APPEARANCES(R.string.lbl_appears_on), LIVE(R.string.lbl_live_group), REMIXES(R.string.lbl_remix_group), } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/list/AlbumDetailListAdapter.kt b/app/src/main/java/org/oxycblt/auxio/detail/list/AlbumDetailListAdapter.kt index 15ece1b35..bb6dcf786 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/list/AlbumDetailListAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/list/AlbumDetailListAdapter.kt @@ -89,6 +89,7 @@ class AlbumDetailListAdapter(private val listener: Listener) : /** * A wrapper around [Disc] signifying that a header should be shown for a disc group. + * * @author Alexander Capehart (OxygenCobalt) */ data class DiscHeader(val inner: Disc?) : Item diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 9003af558..a584ace36 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -158,7 +158,7 @@ constructor( musicSettings.artistSort.artists( if (homeSettings.shouldHideCollaborators) { // Hide Collaborators is enabled, filter out collaborators. - deviceLibrary.artists.filter { !it.isCollaborator } + deviceLibrary.artists.filter { it.explicitAlbums.isNotEmpty() } } else { deviceLibrary.artists }) 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 bcf2fb53e..b2e4553dc 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -314,21 +314,23 @@ interface Album : MusicParent { */ interface Artist : MusicParent { /** - * All of the [Album]s this artist is credited to. Note that any [Song] credited to this artist - * will have it's [Album] considered to be "indirectly" linked to this [Artist], and thus - * included in this list. + * All of the [Album]s this artist is credited to from [explicitAlbums] and [implicitAlbums]. + * Note that any [Song] credited to this artist will have it's [Album] considered to be + * "indirectly" linked to this [Artist], and thus included in this list. */ val albums: List + + /** Albums directly credited to this [Artist] via a "Album Artist" tag. */ + val explicitAlbums: List + + /** Albums indirectly credited to this [Artist] via an "Artist" tag. */ + val implicitAlbums: List + /** * The duration of all [Song]s in the artist, in milliseconds. Will be null if there are no * songs. */ val durationMs: Long? - /** - * Whether this artist is considered a "collaborator", i.e it is not directly credited on any - * [Album]. - */ - val isCollaborator: Boolean /** The [Genre]s of this artist. */ val genres: List } diff --git a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt index 74ab423bf..204acad02 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/device/DeviceMusicImpl.kt @@ -344,8 +344,9 @@ class ArtistImpl( override val songs: List override val albums: List + override val explicitAlbums: List + override val implicitAlbums: List override val durationMs: Long? - override val isCollaborator: Boolean // Note: Append song contents to MusicParent equality so that artists with // the same UID but different songs are not equal. @@ -366,30 +367,30 @@ class ArtistImpl( init { val distinctSongs = mutableSetOf() - val distinctAlbums = mutableSetOf() - - var noAlbums = true + val albumMap = mutableMapOf() for (music in songAlbums) { when (music) { is SongImpl -> { music.link(this) distinctSongs.add(music) - distinctAlbums.add(music.album) + if (albumMap[music.album] == null) { + albumMap[music.album] = false + } } is AlbumImpl -> { music.link(this) - distinctAlbums.add(music) - noAlbums = false + albumMap[music] = true } else -> error("Unexpected input music ${music::class.simpleName}") } } songs = distinctSongs.toList() - albums = distinctAlbums.toList() + albums = albumMap.keys.toList() + explicitAlbums = albumMap.entries.filter { it.value }.map { it.key } + implicitAlbums = albumMap.entries.filterNot { it.value }.map { it.key } durationMs = songs.sumOf { it.durationMs }.nonZeroOrNull() - isCollaborator = noAlbums } /** diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0ab06f20..139cc91c9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,14 +61,16 @@ Mixtape - Mixes + DJ Mixes - Mix + DJ Mix Live Remixes + + Appears on Artist Artists