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:
parent
8939d341e6
commit
ba94d4fa21
7 changed files with 69 additions and 43 deletions
|
@ -5,6 +5,15 @@
|
||||||
#### What's New
|
#### What's New
|
||||||
- Added ability to share a track
|
- 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
|
## 3.1.0
|
||||||
|
|
||||||
#### What's New
|
#### What's New
|
||||||
|
|
|
@ -428,32 +428,42 @@ constructor(
|
||||||
private fun refreshArtistList(artist: Artist, replace: Boolean = false) {
|
private fun refreshArtistList(artist: Artist, replace: Boolean = false) {
|
||||||
logD("Refreshing artist list")
|
logD("Refreshing artist list")
|
||||||
val list = mutableListOf<Item>()
|
val list = mutableListOf<Item>()
|
||||||
val albums = Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING).albums(artist.albums)
|
|
||||||
|
|
||||||
val byReleaseGroup =
|
val grouping =
|
||||||
albums.groupBy {
|
Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING)
|
||||||
// Remap the complicated ReleaseType data structure into an easier
|
.albums(artist.explicitAlbums)
|
||||||
// "AlbumGrouping" enum that will automatically group and sort
|
.groupByTo(sortedMapOf()) {
|
||||||
// the artist's albums.
|
// Remap the complicated ReleaseType data structure into an easier
|
||||||
when (it.releaseType.refinement) {
|
// "AlbumGrouping" enum that will automatically group and sort
|
||||||
ReleaseType.Refinement.LIVE -> AlbumGrouping.LIVE
|
// the artist's albums.
|
||||||
ReleaseType.Refinement.REMIX -> AlbumGrouping.REMIXES
|
when (it.releaseType.refinement) {
|
||||||
null ->
|
ReleaseType.Refinement.LIVE -> AlbumGrouping.LIVE
|
||||||
when (it.releaseType) {
|
ReleaseType.Refinement.REMIX -> AlbumGrouping.REMIXES
|
||||||
is ReleaseType.Album -> AlbumGrouping.ALBUMS
|
null ->
|
||||||
is ReleaseType.EP -> AlbumGrouping.EPS
|
when (it.releaseType) {
|
||||||
is ReleaseType.Single -> AlbumGrouping.SINGLES
|
is ReleaseType.Album -> AlbumGrouping.ALBUMS
|
||||||
is ReleaseType.Compilation -> AlbumGrouping.COMPILATIONS
|
is ReleaseType.EP -> AlbumGrouping.EPS
|
||||||
is ReleaseType.Soundtrack -> AlbumGrouping.SOUNDTRACKS
|
is ReleaseType.Single -> AlbumGrouping.SINGLES
|
||||||
is ReleaseType.Mix -> AlbumGrouping.MIXES
|
is ReleaseType.Compilation -> AlbumGrouping.COMPILATIONS
|
||||||
is ReleaseType.Mixtape -> AlbumGrouping.MIXTAPES
|
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)
|
val header = BasicHeader(entry.key.headerTitleRes)
|
||||||
list.add(Divider(header))
|
list.add(Divider(header))
|
||||||
list.add(header)
|
list.add(header)
|
||||||
|
@ -533,8 +543,9 @@ constructor(
|
||||||
SINGLES(R.string.lbl_singles),
|
SINGLES(R.string.lbl_singles),
|
||||||
COMPILATIONS(R.string.lbl_compilations),
|
COMPILATIONS(R.string.lbl_compilations),
|
||||||
SOUNDTRACKS(R.string.lbl_soundtracks),
|
SOUNDTRACKS(R.string.lbl_soundtracks),
|
||||||
MIXES(R.string.lbl_mixes),
|
DJMIXES(R.string.lbl_mixes),
|
||||||
MIXTAPES(R.string.lbl_mixtapes),
|
MIXTAPES(R.string.lbl_mixtapes),
|
||||||
|
APPEARANCES(R.string.lbl_appears_on),
|
||||||
LIVE(R.string.lbl_live_group),
|
LIVE(R.string.lbl_live_group),
|
||||||
REMIXES(R.string.lbl_remix_group),
|
REMIXES(R.string.lbl_remix_group),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
* A wrapper around [Disc] signifying that a header should be shown for a disc group.
|
||||||
|
*
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
data class DiscHeader(val inner: Disc?) : Item
|
data class DiscHeader(val inner: Disc?) : Item
|
||||||
|
|
|
@ -158,7 +158,7 @@ constructor(
|
||||||
musicSettings.artistSort.artists(
|
musicSettings.artistSort.artists(
|
||||||
if (homeSettings.shouldHideCollaborators) {
|
if (homeSettings.shouldHideCollaborators) {
|
||||||
// Hide Collaborators is enabled, filter out collaborators.
|
// Hide Collaborators is enabled, filter out collaborators.
|
||||||
deviceLibrary.artists.filter { !it.isCollaborator }
|
deviceLibrary.artists.filter { it.explicitAlbums.isNotEmpty() }
|
||||||
} else {
|
} else {
|
||||||
deviceLibrary.artists
|
deviceLibrary.artists
|
||||||
})
|
})
|
||||||
|
|
|
@ -314,21 +314,23 @@ interface Album : MusicParent {
|
||||||
*/
|
*/
|
||||||
interface Artist : MusicParent {
|
interface Artist : MusicParent {
|
||||||
/**
|
/**
|
||||||
* All of the [Album]s this artist is credited to. Note that any [Song] credited to this artist
|
* All of the [Album]s this artist is credited to from [explicitAlbums] and [implicitAlbums].
|
||||||
* will have it's [Album] considered to be "indirectly" linked to this [Artist], and thus
|
* Note that any [Song] credited to this artist will have it's [Album] considered to be
|
||||||
* included in this list.
|
* "indirectly" linked to this [Artist], and thus included in this list.
|
||||||
*/
|
*/
|
||||||
val albums: List<Album>
|
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
|
* The duration of all [Song]s in the artist, in milliseconds. Will be null if there are no
|
||||||
* songs.
|
* songs.
|
||||||
*/
|
*/
|
||||||
val durationMs: Long?
|
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. */
|
/** The [Genre]s of this artist. */
|
||||||
val genres: List<Genre>
|
val genres: List<Genre>
|
||||||
}
|
}
|
||||||
|
|
|
@ -344,8 +344,9 @@ class ArtistImpl(
|
||||||
|
|
||||||
override val songs: List<Song>
|
override val songs: List<Song>
|
||||||
override val albums: List<Album>
|
override val albums: List<Album>
|
||||||
|
override val explicitAlbums: List<Album>
|
||||||
|
override val implicitAlbums: List<Album>
|
||||||
override val durationMs: Long?
|
override val durationMs: Long?
|
||||||
override val isCollaborator: Boolean
|
|
||||||
|
|
||||||
// Note: Append song contents to MusicParent equality so that artists with
|
// Note: Append song contents to MusicParent equality so that artists with
|
||||||
// the same UID but different songs are not equal.
|
// the same UID but different songs are not equal.
|
||||||
|
@ -366,30 +367,30 @@ class ArtistImpl(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val distinctSongs = mutableSetOf<Song>()
|
val distinctSongs = mutableSetOf<Song>()
|
||||||
val distinctAlbums = mutableSetOf<Album>()
|
val albumMap = mutableMapOf<Album, Boolean>()
|
||||||
|
|
||||||
var noAlbums = true
|
|
||||||
|
|
||||||
for (music in songAlbums) {
|
for (music in songAlbums) {
|
||||||
when (music) {
|
when (music) {
|
||||||
is SongImpl -> {
|
is SongImpl -> {
|
||||||
music.link(this)
|
music.link(this)
|
||||||
distinctSongs.add(music)
|
distinctSongs.add(music)
|
||||||
distinctAlbums.add(music.album)
|
if (albumMap[music.album] == null) {
|
||||||
|
albumMap[music.album] = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is AlbumImpl -> {
|
is AlbumImpl -> {
|
||||||
music.link(this)
|
music.link(this)
|
||||||
distinctAlbums.add(music)
|
albumMap[music] = true
|
||||||
noAlbums = false
|
|
||||||
}
|
}
|
||||||
else -> error("Unexpected input music ${music::class.simpleName}")
|
else -> error("Unexpected input music ${music::class.simpleName}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
songs = distinctSongs.toList()
|
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()
|
durationMs = songs.sumOf { it.durationMs }.nonZeroOrNull()
|
||||||
isCollaborator = noAlbums
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -61,14 +61,16 @@
|
||||||
<!-- As in the collection of music -->
|
<!-- As in the collection of music -->
|
||||||
<string name="lbl_mixtape">Mixtape</string>
|
<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) -->
|
<!-- 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) -->
|
<!-- 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 -->
|
<!-- As in music that was performed live -->
|
||||||
<string name="lbl_live_group">Live</string>
|
<string name="lbl_live_group">Live</string>
|
||||||
<!-- As in remixed music -->
|
<!-- As in remixed music -->
|
||||||
<string name="lbl_remix_group">Remixes</string>
|
<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_artist">Artist</string>
|
||||||
<string name="lbl_artists">Artists</string>
|
<string name="lbl_artists">Artists</string>
|
||||||
|
|
Loading…
Reference in a new issue