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.
This commit is contained in:
Alexander Capehart 2023-05-25 12:54:23 -06:00
parent 8939d341e6
commit ba94d4fa21
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
7 changed files with 69 additions and 43 deletions

View file

@ -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

View file

@ -428,32 +428,42 @@ constructor(
private fun refreshArtistList(artist: Artist, replace: Boolean = false) {
logD("Refreshing artist list")
val list = mutableListOf<Item>()
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, List<Album>>)[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),
}

View file

@ -89,6 +89,7 @@ class AlbumDetailListAdapter(private val listener: Listener<Song>) :
/**
* 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

View file

@ -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
})

View file

@ -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<Album>
/** Albums directly credited to this [Artist] via a "Album Artist" tag. */
val explicitAlbums: List<Album>
/** Albums indirectly credited to this [Artist] via an "Artist" tag. */
val implicitAlbums: List<Album>
/**
* 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<Genre>
}

View file

@ -344,8 +344,9 @@ class ArtistImpl(
override val songs: List<Song>
override val albums: List<Album>
override val explicitAlbums: List<Album>
override val implicitAlbums: List<Album>
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<Song>()
val distinctAlbums = mutableSetOf<Album>()
var noAlbums = true
val albumMap = mutableMapOf<Album, Boolean>()
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
}
/**

View file

@ -61,14 +61,16 @@
<!-- As in the collection of music -->
<string name="lbl_mixtape">Mixtape</string>
<!-- As in a compilation of several performances that blend into a single continuous flow of music (Also known as DJ Mixes) -->
<string name="lbl_mixes">Mixes</string>
<string name="lbl_mixes">DJ Mixes</string>
<!-- As in a compilation of several performances that blend into a single continuous flow of music (Also known as DJ Mixes) -->
<string name="lbl_mix">Mix</string>
<string name="lbl_mix">DJ Mix</string>
<!-- As in music that was performed live -->
<string name="lbl_live_group">Live</string>
<!-- As in remixed music -->
<string name="lbl_remix_group">Remixes</string>
<!-- As in albums that an artist only partially contributed to -->
<string name="lbl_appears_on">Appears on</string>
<string name="lbl_artist">Artist</string>
<string name="lbl_artists">Artists</string>