music: use set for child information
Use sets for all child music information. Unlike parent information, which usually has an ordering derived from file information, child music information more or less doesn't, and will be consistently re-interpreted by the app to apply user-configured sorts.
This commit is contained in:
parent
d22de34fd3
commit
31d647123f
11 changed files with 100 additions and 48 deletions
|
@ -480,7 +480,7 @@ constructor(
|
||||||
// implicit album list into the mapping.
|
// implicit album list into the mapping.
|
||||||
logD("Implicit albums present, adding to list")
|
logD("Implicit albums present, adding to list")
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
(grouping as MutableMap<AlbumGrouping, List<Album>>)[AlbumGrouping.APPEARANCES] =
|
(grouping as MutableMap<AlbumGrouping, Collection<Album>>)[AlbumGrouping.APPEARANCES] =
|
||||||
artist.implicitAlbums
|
artist.implicitAlbums
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,7 +490,7 @@ constructor(
|
||||||
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)
|
||||||
list.addAll(entry.value)
|
list.addAll(ARTIST_ALBUM_SORT.albums(entry.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Artists may not be linked to any songs, only include a header entry if we have any.
|
// Artists may not be linked to any songs, only include a header entry if we have any.
|
||||||
|
@ -519,7 +519,7 @@ constructor(
|
||||||
val artistHeader = BasicHeader(R.string.lbl_artists)
|
val artistHeader = BasicHeader(R.string.lbl_artists)
|
||||||
list.add(Divider(artistHeader))
|
list.add(Divider(artistHeader))
|
||||||
list.add(artistHeader)
|
list.add(artistHeader)
|
||||||
list.addAll(genre.artists)
|
list.addAll(GENRE_ARTIST_SORT.artists(genre.artists))
|
||||||
|
|
||||||
val songHeader = SortHeader(R.string.lbl_songs)
|
val songHeader = SortHeader(R.string.lbl_songs)
|
||||||
list.add(Divider(songHeader))
|
list.add(Divider(songHeader))
|
||||||
|
@ -576,4 +576,9 @@ constructor(
|
||||||
LIVE(R.string.lbl_live_group),
|
LIVE(R.string.lbl_live_group),
|
||||||
REMIXES(R.string.lbl_remix_group),
|
REMIXES(R.string.lbl_remix_group),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
val ARTIST_ALBUM_SORT = Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING)
|
||||||
|
val GENRE_ARTIST_SORT = Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -379,7 +379,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
||||||
* @param desc The content description to describe the bound data.
|
* @param desc The content description to describe the bound data.
|
||||||
* @param errorRes The resource of the error drawable to use if the cover cannot be loaded.
|
* @param errorRes The resource of the error drawable to use if the cover cannot be loaded.
|
||||||
*/
|
*/
|
||||||
fun bind(songs: List<Song>, desc: String, @DrawableRes errorRes: Int) {
|
fun bind(songs: Collection<Song>, desc: String, @DrawableRes errorRes: Int) {
|
||||||
val request =
|
val request =
|
||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
.data(songs)
|
.data(songs)
|
||||||
|
|
|
@ -27,22 +27,22 @@ import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
|
||||||
class SongKeyer @Inject constructor(private val coverExtractor: CoverExtractor) :
|
class SongKeyer @Inject constructor(private val coverExtractor: CoverExtractor) :
|
||||||
Keyer<List<Song>> {
|
Keyer<Collection<Song>> {
|
||||||
override fun key(data: List<Song>, options: Options) =
|
override fun key(data: Collection<Song>, options: Options) =
|
||||||
"${coverExtractor.computeCoverOrdering(data).hashCode()}"
|
"${coverExtractor.computeCoverOrdering(data).hashCode()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
class SongCoverFetcher
|
class SongCoverFetcher
|
||||||
private constructor(
|
private constructor(
|
||||||
private val songs: List<Song>,
|
private val songs: Collection<Song>,
|
||||||
private val size: Size,
|
private val size: Size,
|
||||||
private val coverExtractor: CoverExtractor,
|
private val coverExtractor: CoverExtractor,
|
||||||
) : Fetcher {
|
) : Fetcher {
|
||||||
override suspend fun fetch() = coverExtractor.extract(songs, size)
|
override suspend fun fetch() = coverExtractor.extract(songs, size)
|
||||||
|
|
||||||
class Factory @Inject constructor(private val coverExtractor: CoverExtractor) :
|
class Factory @Inject constructor(private val coverExtractor: CoverExtractor) :
|
||||||
Fetcher.Factory<List<Song>> {
|
Fetcher.Factory<Collection<Song>> {
|
||||||
override fun create(data: List<Song>, options: Options, imageLoader: ImageLoader) =
|
override fun create(data: Collection<Song>, options: Options, imageLoader: ImageLoader) =
|
||||||
SongCoverFetcher(data, options.size, coverExtractor)
|
SongCoverFetcher(data, options.size, coverExtractor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ constructor(
|
||||||
* will be returned of a mosaic composed of four album covers ordered by
|
* will be returned of a mosaic composed of four album covers ordered by
|
||||||
* [computeCoverOrdering]. Otherwise, a [SourceResult] of one album cover will be returned.
|
* [computeCoverOrdering]. Otherwise, a [SourceResult] of one album cover will be returned.
|
||||||
*/
|
*/
|
||||||
suspend fun extract(songs: List<Song>, size: Size): FetchResult? {
|
suspend fun extract(songs: Collection<Song>, size: Size): FetchResult? {
|
||||||
val albums = computeCoverOrdering(songs)
|
val albums = computeCoverOrdering(songs)
|
||||||
val streams = mutableListOf<InputStream>()
|
val streams = mutableListOf<InputStream>()
|
||||||
for (album in albums) {
|
for (album in albums) {
|
||||||
|
@ -117,7 +117,7 @@ constructor(
|
||||||
* by their names. "Representation" is defined by how many [Song]s were found to be linked to
|
* by their names. "Representation" is defined by how many [Song]s were found to be linked to
|
||||||
* the given [Album] in the given [Song] list.
|
* the given [Album] in the given [Song] list.
|
||||||
*/
|
*/
|
||||||
fun computeCoverOrdering(songs: List<Song>): List<Album> {
|
fun computeCoverOrdering(songs: Collection<Song>): List<Album> {
|
||||||
// TODO: Start short-circuiting in more places
|
// TODO: Start short-circuiting in more places
|
||||||
if (songs.isEmpty()) return listOf()
|
if (songs.isEmpty()) return listOf()
|
||||||
if (songs.size == 1) return listOf(songs.first().album)
|
if (songs.size == 1) return listOf(songs.first().album)
|
||||||
|
@ -150,7 +150,7 @@ constructor(
|
||||||
MediaMetadataRetriever().run {
|
MediaMetadataRetriever().run {
|
||||||
// This call is time-consuming but it also doesn't seem to hold up the main thread,
|
// This call is time-consuming but it also doesn't seem to hold up the main thread,
|
||||||
// so it's probably fine not to wrap it.rmt
|
// so it's probably fine not to wrap it.rmt
|
||||||
setDataSource(context, album.songs[0].uri)
|
setDataSource(context, album.coverUri.song)
|
||||||
|
|
||||||
// Get the embedded picture from MediaMetadataRetriever, which will return a full
|
// Get the embedded picture from MediaMetadataRetriever, which will return a full
|
||||||
// ByteArray of the cover without any compression artifacts.
|
// ByteArray of the cover without any compression artifacts.
|
||||||
|
@ -161,7 +161,7 @@ constructor(
|
||||||
private suspend fun extractExoplayerCover(album: Album): InputStream? {
|
private suspend fun extractExoplayerCover(album: Album): InputStream? {
|
||||||
val tracks =
|
val tracks =
|
||||||
MetadataRetriever.retrieveMetadata(
|
MetadataRetriever.retrieveMetadata(
|
||||||
mediaSourceFactory, MediaItem.fromUri(album.songs[0].uri))
|
mediaSourceFactory, MediaItem.fromUri(album.coverUri.song))
|
||||||
.asDeferred()
|
.asDeferred()
|
||||||
.await()
|
.await()
|
||||||
|
|
||||||
|
@ -207,7 +207,9 @@ constructor(
|
||||||
|
|
||||||
private suspend fun extractMediaStoreCover(album: Album) =
|
private suspend fun extractMediaStoreCover(album: Album) =
|
||||||
// Eliminate any chance that this blocking call might mess up the loading process
|
// Eliminate any chance that this blocking call might mess up the loading process
|
||||||
withContext(Dispatchers.IO) { context.contentResolver.openInputStream(album.coverUri) }
|
withContext(Dispatchers.IO) {
|
||||||
|
context.contentResolver.openInputStream(album.coverUri.mediaStore)
|
||||||
|
}
|
||||||
|
|
||||||
/** Derived from phonograph: https://github.com/kabouzeid/Phonograph */
|
/** Derived from phonograph: https://github.com/kabouzeid/Phonograph */
|
||||||
private fun createMosaic(streams: List<InputStream>, size: Size): FetchResult {
|
private fun createMosaic(streams: List<InputStream>, size: Size): FetchResult {
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Auxio Project
|
||||||
|
* CoverUri.kt is part of Auxio.
|
||||||
|
*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.oxycblt.auxio.image.extractor
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundle of [Uri] information used in [CoverExtractor] to ensure consistent [Uri] use when loading
|
||||||
|
* images.
|
||||||
|
* @param mediaStore The album cover [Uri] obtained from MediaStore.
|
||||||
|
* @param song The [Uri] of the first song (by track) of the album, which can also be used to
|
||||||
|
* obtain an album cover.
|
||||||
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
|
*/
|
||||||
|
data class CoverUri(val mediaStore: Uri, val song: Uri)
|
|
@ -27,6 +27,7 @@ import java.util.UUID
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import org.oxycblt.auxio.image.extractor.CoverUri
|
||||||
import org.oxycblt.auxio.list.Item
|
import org.oxycblt.auxio.list.Item
|
||||||
import org.oxycblt.auxio.music.fs.MimeType
|
import org.oxycblt.auxio.music.fs.MimeType
|
||||||
import org.oxycblt.auxio.music.fs.Path
|
import org.oxycblt.auxio.music.fs.Path
|
||||||
|
@ -225,7 +226,7 @@ sealed interface Music : Item {
|
||||||
*/
|
*/
|
||||||
sealed interface MusicParent : Music {
|
sealed interface MusicParent : Music {
|
||||||
/** The child [Song]s of this [MusicParent]. */
|
/** The child [Song]s of this [MusicParent]. */
|
||||||
val songs: List<Song>
|
val songs: Collection<Song>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -296,7 +297,7 @@ interface Album : MusicParent {
|
||||||
* The URI to a MediaStore-provided album cover. These images will be fast to load, but at the
|
* The URI to a MediaStore-provided album cover. These images will be fast to load, but at the
|
||||||
* cost of image quality.
|
* cost of image quality.
|
||||||
*/
|
*/
|
||||||
val coverUri: Uri
|
val coverUri: CoverUri
|
||||||
/** The duration of all songs in the album, in milliseconds. */
|
/** The duration of all songs in the album, in milliseconds. */
|
||||||
val durationMs: Long
|
val durationMs: Long
|
||||||
/** The earliest date a song in this album was added, as a unix epoch timestamp. */
|
/** The earliest date a song in this album was added, as a unix epoch timestamp. */
|
||||||
|
@ -321,14 +322,11 @@ interface Artist : MusicParent {
|
||||||
* Note that any [Song] credited to this artist will have it's [Album] considered to be
|
* 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.
|
* "indirectly" linked to this [Artist], and thus included in this list.
|
||||||
*/
|
*/
|
||||||
val albums: List<Album>
|
val albums: Collection<Album>
|
||||||
|
|
||||||
/** Albums directly credited to this [Artist] via a "Album Artist" tag. */
|
/** Albums directly credited to this [Artist] via a "Album Artist" tag. */
|
||||||
val explicitAlbums: List<Album>
|
val explicitAlbums: Collection<Album>
|
||||||
|
|
||||||
/** Albums indirectly credited to this [Artist] via an "Artist" tag. */
|
/** Albums indirectly credited to this [Artist] via an "Artist" tag. */
|
||||||
val implicitAlbums: List<Album>
|
val implicitAlbums: Collection<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.
|
||||||
|
@ -345,7 +343,7 @@ interface Artist : MusicParent {
|
||||||
*/
|
*/
|
||||||
interface Genre : MusicParent {
|
interface Genre : MusicParent {
|
||||||
/** The artists indirectly linked to by the [Artist]s of this [Genre]. */
|
/** The artists indirectly linked to by the [Artist]s of this [Genre]. */
|
||||||
val artists: List<Artist>
|
val artists: Collection<Artist>
|
||||||
/** The total duration of the songs in this genre, in milliseconds. */
|
/** The total duration of the songs in this genre, in milliseconds. */
|
||||||
val durationMs: Long
|
val durationMs: Long
|
||||||
}
|
}
|
||||||
|
@ -356,6 +354,7 @@ interface Genre : MusicParent {
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
interface Playlist : MusicParent {
|
interface Playlist : MusicParent {
|
||||||
|
override val songs: List<Song>
|
||||||
/** The total duration of the songs in this genre, in milliseconds. */
|
/** The total duration of the songs in this genre, in milliseconds. */
|
||||||
val durationMs: Long
|
val durationMs: Long
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,7 @@ class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: Mu
|
||||||
} else {
|
} else {
|
||||||
// Need to initialize this grouping.
|
// Need to initialize this grouping.
|
||||||
albumGrouping[albumKey] =
|
albumGrouping[albumKey] =
|
||||||
Grouping(PrioritizedRaw(song.rawAlbum, song), mutableListOf(song))
|
Grouping(PrioritizedRaw(song.rawAlbum, song), mutableSetOf(song))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group the song into each of it's artists.
|
// Group the song into each of it's artists.
|
||||||
|
@ -174,7 +174,7 @@ class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: Mu
|
||||||
} else {
|
} else {
|
||||||
// Need to initialize this grouping.
|
// Need to initialize this grouping.
|
||||||
artistGrouping[artistKey] =
|
artistGrouping[artistKey] =
|
||||||
Grouping(PrioritizedRaw(rawArtist, song), mutableListOf(song))
|
Grouping(PrioritizedRaw(rawArtist, song), mutableSetOf(song))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: Mu
|
||||||
} else {
|
} else {
|
||||||
// Need to initialize this grouping.
|
// Need to initialize this grouping.
|
||||||
genreGrouping[genreKey] =
|
genreGrouping[genreKey] =
|
||||||
Grouping(PrioritizedRaw(rawGenre, song), mutableListOf(song))
|
Grouping(PrioritizedRaw(rawGenre, song), mutableSetOf(song))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ class DeviceLibraryFactoryImpl @Inject constructor(private val musicSettings: Mu
|
||||||
} else {
|
} else {
|
||||||
// Need to initialize this grouping.
|
// Need to initialize this grouping.
|
||||||
artistGrouping[key] =
|
artistGrouping[key] =
|
||||||
Grouping(PrioritizedRaw(rawArtist, album), mutableListOf(album))
|
Grouping(PrioritizedRaw(rawArtist, album), mutableSetOf(album))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.oxycblt.auxio.music.device
|
package org.oxycblt.auxio.music.device
|
||||||
|
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
import org.oxycblt.auxio.image.extractor.CoverUri
|
||||||
import org.oxycblt.auxio.list.Sort
|
import org.oxycblt.auxio.list.Sort
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
|
@ -254,15 +255,16 @@ class AlbumImpl(
|
||||||
override val name = Name.Known.from(rawAlbum.name, rawAlbum.sortName, musicSettings)
|
override val name = Name.Known.from(rawAlbum.name, rawAlbum.sortName, musicSettings)
|
||||||
override val dates: Date.Range?
|
override val dates: Date.Range?
|
||||||
override val releaseType = rawAlbum.releaseType ?: ReleaseType.Album(null)
|
override val releaseType = rawAlbum.releaseType ?: ReleaseType.Album(null)
|
||||||
override val coverUri = rawAlbum.mediaStoreId.toCoverUri()
|
override val coverUri = CoverUri(rawAlbum.mediaStoreId.toCoverUri(), grouping.raw.src.uri)
|
||||||
override val durationMs: Long
|
override val durationMs: Long
|
||||||
override val dateAdded: Long
|
override val dateAdded: Long
|
||||||
|
|
||||||
override val songs: List<Song>
|
|
||||||
private val _artists = mutableListOf<ArtistImpl>()
|
private val _artists = mutableListOf<ArtistImpl>()
|
||||||
override val artists: List<Artist>
|
override val artists: List<Artist>
|
||||||
get() = _artists
|
get() = _artists
|
||||||
|
|
||||||
|
override val songs: Set<Song> = grouping.music
|
||||||
|
|
||||||
private var hashCode = uid.hashCode()
|
private var hashCode = uid.hashCode()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -298,7 +300,6 @@ class AlbumImpl(
|
||||||
dates = if (min != null && max != null) Date.Range(min, max) else null
|
dates = if (min != null && max != null) Date.Range(min, max) else null
|
||||||
durationMs = totalDuration
|
durationMs = totalDuration
|
||||||
dateAdded = earliestDateAdded
|
dateAdded = earliestDateAdded
|
||||||
songs = Sort(Sort.Mode.ByTrack, Sort.Direction.ASCENDING).songs(grouping.music)
|
|
||||||
|
|
||||||
hashCode = 31 * hashCode + rawAlbum.hashCode()
|
hashCode = 31 * hashCode + rawAlbum.hashCode()
|
||||||
hashCode = 31 * hashCode + songs.hashCode()
|
hashCode = 31 * hashCode + songs.hashCode()
|
||||||
|
@ -363,10 +364,10 @@ class ArtistImpl(grouping: Grouping<RawArtist, Music>, musicSettings: MusicSetti
|
||||||
rawArtist.name?.let { Name.Known.from(it, rawArtist.sortName, musicSettings) }
|
rawArtist.name?.let { Name.Known.from(it, rawArtist.sortName, musicSettings) }
|
||||||
?: Name.Unknown(R.string.def_artist)
|
?: Name.Unknown(R.string.def_artist)
|
||||||
|
|
||||||
override val songs: List<Song>
|
override val songs: Set<Song>
|
||||||
override val albums: List<Album>
|
override val albums: Set<Album>
|
||||||
override val explicitAlbums: List<Album>
|
override val explicitAlbums: Set<Album>
|
||||||
override val implicitAlbums: List<Album>
|
override val implicitAlbums: Set<Album>
|
||||||
override val durationMs: Long?
|
override val durationMs: Long?
|
||||||
|
|
||||||
override lateinit var genres: List<Genre>
|
override lateinit var genres: List<Genre>
|
||||||
|
@ -394,10 +395,10 @@ class ArtistImpl(grouping: Grouping<RawArtist, Music>, musicSettings: MusicSetti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
songs = Sort(Sort.Mode.ByDate, Sort.Direction.ASCENDING).songs(distinctSongs)
|
songs = distinctSongs
|
||||||
albums = Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING).albums(albumMap.keys)
|
albums = albumMap.keys
|
||||||
explicitAlbums = albums.filter { unlikelyToBeNull(albumMap[it]) }
|
explicitAlbums = albums.filterTo(mutableSetOf()) { albumMap[it] == true }
|
||||||
implicitAlbums = albums.filterNot { unlikelyToBeNull(albumMap[it]) }
|
implicitAlbums = albums.filterNotTo(mutableSetOf()) { albumMap[it] == true }
|
||||||
durationMs = songs.sumOf { it.durationMs }.nonZeroOrNull()
|
durationMs = songs.sumOf { it.durationMs }.nonZeroOrNull()
|
||||||
|
|
||||||
hashCode = 31 * hashCode + rawArtist.hashCode()
|
hashCode = 31 * hashCode + rawArtist.hashCode()
|
||||||
|
@ -457,8 +458,8 @@ class GenreImpl(grouping: Grouping<RawGenre, SongImpl>, musicSettings: MusicSett
|
||||||
rawGenre.name?.let { Name.Known.from(it, rawGenre.name, musicSettings) }
|
rawGenre.name?.let { Name.Known.from(it, rawGenre.name, musicSettings) }
|
||||||
?: Name.Unknown(R.string.def_genre)
|
?: Name.Unknown(R.string.def_genre)
|
||||||
|
|
||||||
override val songs: List<Song>
|
override val songs: Set<Song>
|
||||||
override val artists: List<Artist>
|
override val artists: Set<Artist>
|
||||||
override val durationMs: Long
|
override val durationMs: Long
|
||||||
|
|
||||||
private var hashCode = uid.hashCode()
|
private var hashCode = uid.hashCode()
|
||||||
|
@ -473,8 +474,8 @@ class GenreImpl(grouping: Grouping<RawGenre, SongImpl>, musicSettings: MusicSett
|
||||||
totalDuration += song.durationMs
|
totalDuration += song.durationMs
|
||||||
}
|
}
|
||||||
|
|
||||||
songs = Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING).songs(grouping.music)
|
songs = grouping.music
|
||||||
artists = Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING).artists(distinctArtists)
|
artists = distinctArtists
|
||||||
durationMs = totalDuration
|
durationMs = totalDuration
|
||||||
|
|
||||||
hashCode = 31 * hashCode + rawGenre.hashCode()
|
hashCode = 31 * hashCode + rawGenre.hashCode()
|
||||||
|
@ -510,7 +511,3 @@ class GenreImpl(grouping: Grouping<RawGenre, SongImpl>, musicSettings: MusicSett
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Grouping<R, M : Music>(var raw: PrioritizedRaw<R, M>, val music: MutableList<M>)
|
|
||||||
|
|
||||||
data class PrioritizedRaw<R, M : Music>(val inner: R, val src: M)
|
|
||||||
|
|
|
@ -237,3 +237,21 @@ data class RawGenre(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents grouped music information and the prioritized raw information to eventually derive a
|
||||||
|
* [Music] implementation instance from.
|
||||||
|
*
|
||||||
|
* @param raw The current [PrioritizedRaw] that will be used for the finalized music information.
|
||||||
|
* @param music The child [Music] instances of the music information to be created.
|
||||||
|
*/
|
||||||
|
data class Grouping<R, M : Music>(var raw: PrioritizedRaw<R, M>, val music: MutableSet<M>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a [RawAlbum], [RawArtist], or [RawGenre] specifically chosen to create a [Music]
|
||||||
|
* instance from due to it being the most likely source of truth.
|
||||||
|
*
|
||||||
|
* @param inner The raw music instance that will be used.
|
||||||
|
* @param src The [Music] instance that the raw information was derived from.
|
||||||
|
*/
|
||||||
|
data class PrioritizedRaw<R, M : Music>(val inner: R, val src: M)
|
||||||
|
|
|
@ -367,7 +367,7 @@ constructor(
|
||||||
.setSubtitle(song.artists.resolveNames(context))
|
.setSubtitle(song.artists.resolveNames(context))
|
||||||
// Since we usually have to load many songs into the queue, use the
|
// Since we usually have to load many songs into the queue, use the
|
||||||
// MediaStore URI instead of loading a bitmap.
|
// MediaStore URI instead of loading a bitmap.
|
||||||
.setIconUri(song.album.coverUri)
|
.setIconUri(song.album.coverUri.mediaStore)
|
||||||
.setMediaUri(song.uri)
|
.setMediaUri(song.uri)
|
||||||
.build()
|
.build()
|
||||||
// Store the item index so we can then use the analogous index in the
|
// Store the item index so we can then use the analogous index in the
|
||||||
|
|
|
@ -286,7 +286,7 @@ fun Context.share(parent: MusicParent) = share(parent.songs)
|
||||||
*
|
*
|
||||||
* @param songs The [Song]s to share.
|
* @param songs The [Song]s to share.
|
||||||
*/
|
*/
|
||||||
fun Context.share(songs: List<Song>) {
|
fun Context.share(songs: Collection<Song>) {
|
||||||
if (songs.isEmpty()) return
|
if (songs.isEmpty()) return
|
||||||
logD("Showing sharesheet for ${songs.size} songs")
|
logD("Showing sharesheet for ${songs.size} songs")
|
||||||
val builder = ShareCompat.IntentBuilder(this)
|
val builder = ShareCompat.IntentBuilder(this)
|
||||||
|
|
Loading…
Reference in a new issue