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
|
||||
- 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
|
||||
|
|
|
@ -428,10 +428,11 @@ 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 {
|
||||
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.
|
||||
|
@ -445,15 +446,24 @@ constructor(
|
|||
is ReleaseType.Single -> AlbumGrouping.SINGLES
|
||||
is ReleaseType.Compilation -> AlbumGrouping.COMPILATIONS
|
||||
is ReleaseType.Soundtrack -> AlbumGrouping.SOUNDTRACKS
|
||||
is ReleaseType.Mix -> AlbumGrouping.MIXES
|
||||
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),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue