Fix bugs w/music loading
Fix bugs with how genres/album names are displayed.
This commit is contained in:
parent
ec45a020a5
commit
602cc2153f
14 changed files with 87 additions and 71 deletions
|
@ -12,7 +12,6 @@ import androidx.navigation.fragment.navArgs
|
|||
import org.oxycblt.auxio.ClickListener
|
||||
import org.oxycblt.auxio.databinding.FragmentArtistDetailBinding
|
||||
import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter
|
||||
import org.oxycblt.auxio.library.DetailViewModel
|
||||
import org.oxycblt.auxio.music.MusicViewModel
|
||||
import org.oxycblt.auxio.music.models.Album
|
||||
import org.oxycblt.auxio.theme.applyDivider
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package org.oxycblt.auxio.library
|
||||
package org.oxycblt.auxio.detail
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class DetailSongAdapter(
|
|||
holder.bind(data[position])
|
||||
}
|
||||
|
||||
// Generic ViewHolder for an album
|
||||
// Generic ViewHolder for a song
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAlbumSongBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
|
|
@ -37,13 +37,13 @@ private val ID3_GENRES = arrayOf(
|
|||
const val PAREN_FILTER = "()"
|
||||
|
||||
// Convert legacy ID3 genres to a named genre
|
||||
fun String.toNamedGenre(): String {
|
||||
fun String.toNamedGenre(): String? {
|
||||
// Strip the genres of any parentheses, and convert it to an int
|
||||
val intGenre = this.filterNot {
|
||||
PAREN_FILTER.indexOf(it) > -1
|
||||
}.toInt()
|
||||
|
||||
return ID3_GENRES.getOrNull(intGenre) ?: ""
|
||||
return ID3_GENRES.getOrNull(intGenre)
|
||||
}
|
||||
|
||||
// Convert a song to its URI
|
||||
|
|
|
@ -69,20 +69,32 @@ class MusicViewModel(private val app: Application) : ViewModel() {
|
|||
ioScope.launch {
|
||||
val start = System.currentTimeMillis()
|
||||
|
||||
val loader = MusicLoader(app.contentResolver)
|
||||
// Get the placeholder strings, which are used by MusicLoader & MusicSorter for
|
||||
// any music that doesn't have metadata.
|
||||
val genrePlaceholder = app.getString(R.string.placeholder_genre)
|
||||
val artistPlaceholder = app.getString(R.string.placeholder_artist)
|
||||
val albumPlaceholder = app.getString(R.string.placeholder_album)
|
||||
|
||||
val loader = MusicLoader(
|
||||
app.contentResolver,
|
||||
|
||||
genrePlaceholder,
|
||||
artistPlaceholder,
|
||||
albumPlaceholder
|
||||
)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (loader.response == MusicLoaderResponse.DONE) {
|
||||
// If the loading succeeds, then process the songs and set them.
|
||||
// If the loading succeeds, then sort the songs and update the value
|
||||
val sorter = MusicSorter(
|
||||
loader.genres,
|
||||
loader.artists,
|
||||
loader.albums,
|
||||
loader.songs,
|
||||
|
||||
app.getString(R.string.placeholder_unknown_genre),
|
||||
app.getString(R.string.placeholder_unknown_artist),
|
||||
app.getString(R.string.placeholder_unknown_album)
|
||||
genrePlaceholder,
|
||||
artistPlaceholder,
|
||||
albumPlaceholder
|
||||
)
|
||||
|
||||
mSongs.value = sorter.songs.toList()
|
||||
|
|
|
@ -4,9 +4,9 @@ import android.net.Uri
|
|||
|
||||
// Abstraction for Song
|
||||
data class Album(
|
||||
val id: Long = 0L,
|
||||
var name: String = "",
|
||||
val artistName: String = "", // only used for sorting. Use artist.name instead.
|
||||
val id: Long = -1,
|
||||
var name: String,
|
||||
val artistName: String,
|
||||
val coverUri: Uri = Uri.EMPTY,
|
||||
val year: Int = 0
|
||||
) {
|
||||
|
|
|
@ -2,9 +2,9 @@ package org.oxycblt.auxio.music.models
|
|||
|
||||
// Abstraction for albums
|
||||
data class Artist(
|
||||
val id: Long = 0,
|
||||
var name: String = "",
|
||||
val genres: MutableList<Genre> = mutableListOf(Genre())
|
||||
val id: Long = -1,
|
||||
var name: String,
|
||||
val genres: MutableList<Genre> = mutableListOf()
|
||||
) {
|
||||
val albums = mutableListOf<Album>()
|
||||
var genre = ""
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package org.oxycblt.auxio.music.models
|
||||
|
||||
data class Genre(
|
||||
val id: Long = 0,
|
||||
var name: String = "",
|
||||
val id: Long = -1,
|
||||
var name: String,
|
||||
) {
|
||||
val artists = mutableListOf<Artist>()
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@ import android.text.format.DateUtils
|
|||
data class Song(
|
||||
val id: Long,
|
||||
var name: String,
|
||||
val albumName: String, // Only used for sorting. Use album.title for everything else.
|
||||
val albumId: Long,
|
||||
val track: Int,
|
||||
val duration: Long
|
||||
) {
|
||||
lateinit var album: Album
|
||||
|
||||
val seconds = duration / 1000
|
||||
val formattedDuration = DateUtils.formatElapsedTime(seconds)
|
||||
val formattedDuration: String = DateUtils.formatElapsedTime(seconds)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,13 @@ enum class MusicLoaderResponse {
|
|||
}
|
||||
|
||||
// Class that loads music from the FileSystem.
|
||||
class MusicLoader(private val resolver: ContentResolver) {
|
||||
class MusicLoader(
|
||||
private val resolver: ContentResolver,
|
||||
|
||||
private val genrePlaceholder: String,
|
||||
private val artistPlaceholder: String,
|
||||
private val albumPlaceholder: String,
|
||||
) {
|
||||
|
||||
var genres = mutableListOf<Genre>()
|
||||
var artists = mutableListOf<Artist>()
|
||||
|
@ -78,12 +84,12 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idIndex)
|
||||
var name = cursor.getString(nameIndex) ?: ""
|
||||
var name = cursor.getString(nameIndex) ?: genrePlaceholder
|
||||
|
||||
// If a genre is still in an old int-based format [Android formats it as "(INT)"],
|
||||
// convert that to the corresponding ID3 genre.
|
||||
if (name.contains(Regex("[0123456789)]"))) {
|
||||
name = name.toNamedGenre()
|
||||
name = name.toNamedGenre() ?: genrePlaceholder
|
||||
}
|
||||
|
||||
genres.add(
|
||||
|
@ -124,7 +130,7 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idIndex)
|
||||
val name = cursor.getString(nameIndex) ?: ""
|
||||
val name = cursor.getString(nameIndex) ?: artistPlaceholder
|
||||
|
||||
// If an artist has already been added [Which is very likely due to how genres
|
||||
// are processed], add the genre to the existing artist instead of creating a
|
||||
|
@ -182,12 +188,16 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idIndex)
|
||||
val name = cursor.getString(nameIndex) ?: ""
|
||||
val artist = cursor.getString(artistIndex) ?: ""
|
||||
var name = cursor.getString(nameIndex) ?: albumPlaceholder
|
||||
val artist = cursor.getString(artistIndex)
|
||||
val year = cursor.getInt(yearIndex)
|
||||
|
||||
val coverUri = id.toAlbumArtURI()
|
||||
|
||||
// Sometimes the android system will return 0 for an album name, update
|
||||
// it properly if that happens.
|
||||
name = if (name == "0") albumPlaceholder else name
|
||||
|
||||
albums.add(
|
||||
Album(
|
||||
id, name, artist,
|
||||
|
@ -219,9 +229,9 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
Media._ID, // 0
|
||||
Media.DISPLAY_NAME, // 1
|
||||
Media.TITLE, // 2
|
||||
Media.ALBUM, // 4
|
||||
Media.TRACK, // 6
|
||||
Media.DURATION // 7
|
||||
Media.ALBUM_ID, // 3
|
||||
Media.TRACK, // 4
|
||||
Media.DURATION // 5
|
||||
),
|
||||
Media.IS_MUSIC + "=1", null,
|
||||
Media.DEFAULT_SORT_ORDER
|
||||
|
@ -231,20 +241,20 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
val idIndex = cursor.getColumnIndexOrThrow(Media._ID)
|
||||
val fileIndex = cursor.getColumnIndexOrThrow(Media.DISPLAY_NAME)
|
||||
val titleIndex = cursor.getColumnIndexOrThrow(Media.TITLE)
|
||||
val albumIndex = cursor.getColumnIndexOrThrow(Media.ALBUM)
|
||||
val albumIndex = cursor.getColumnIndexOrThrow(Media.ALBUM_ID)
|
||||
val trackIndex = cursor.getColumnIndexOrThrow(Media.TRACK)
|
||||
val durationIndex = cursor.getColumnIndexOrThrow(Media.DURATION)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idIndex)
|
||||
val title = cursor.getString(titleIndex) ?: cursor.getString(fileIndex)
|
||||
val album = cursor.getString(albumIndex) ?: ""
|
||||
val albumId = cursor.getLong(albumIndex)
|
||||
val track = cursor.getInt(trackIndex)
|
||||
val duration = cursor.getLong(durationIndex)
|
||||
|
||||
songs.add(
|
||||
Song(
|
||||
id, title, album,
|
||||
id, title, albumId,
|
||||
track, duration
|
||||
)
|
||||
)
|
||||
|
@ -255,7 +265,7 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
|
||||
// Remove dupes
|
||||
songs = songs.distinctBy {
|
||||
it.name to it.albumName to it.track to it.duration
|
||||
it.name to it.albumId to it.track to it.duration
|
||||
}.toMutableList()
|
||||
|
||||
Log.d(
|
||||
|
|
|
@ -21,7 +21,6 @@ class MusicSorter(
|
|||
sortAlbumsIntoArtists()
|
||||
sortArtistsIntoGenres()
|
||||
|
||||
addPlaceholders()
|
||||
finalizeMusic()
|
||||
}
|
||||
|
||||
|
@ -31,8 +30,9 @@ class MusicSorter(
|
|||
val unknownSongs = songs.toMutableList()
|
||||
|
||||
for (album in albums) {
|
||||
// Find all songs that match the current album title
|
||||
val albumSongs = songs.filter { it.albumName == album.name }
|
||||
// Find all songs that match the current album ID to prevent any bugs w/comparing names.
|
||||
// This cant be done with artists/genres sadly.
|
||||
val albumSongs = songs.filter { it.albumId == album.id }
|
||||
|
||||
// Then add them to the album
|
||||
for (song in albumSongs) {
|
||||
|
@ -40,8 +40,6 @@ class MusicSorter(
|
|||
album.songs.add(song)
|
||||
}
|
||||
|
||||
album.finalize()
|
||||
|
||||
unknownSongs.removeAll(albumSongs)
|
||||
}
|
||||
|
||||
|
@ -49,15 +47,16 @@ class MusicSorter(
|
|||
if (unknownSongs.size > 0) {
|
||||
|
||||
// Reuse an existing unknown album if one is found
|
||||
val unknownAlbum = albums.find { it.name == "" } ?: Album()
|
||||
val unknownAlbum = Album(
|
||||
name = albumPlaceholder,
|
||||
artistName = artistPlaceholder
|
||||
)
|
||||
|
||||
for (song in unknownSongs) {
|
||||
song.album = unknownAlbum
|
||||
unknownAlbum.songs.add(song)
|
||||
}
|
||||
|
||||
unknownAlbum.finalize()
|
||||
|
||||
albums.add(unknownAlbum)
|
||||
|
||||
Log.d(
|
||||
|
@ -76,14 +75,14 @@ class MusicSorter(
|
|||
// Find all albums that match the current artist name
|
||||
val artistAlbums = albums.filter { it.artistName == artist.name }
|
||||
|
||||
Log.d(this::class.simpleName, artist.id.toString())
|
||||
|
||||
// Then add them to the artist, along with refreshing the amount of albums
|
||||
for (album in artistAlbums) {
|
||||
album.artist = artist
|
||||
artist.albums.add(album)
|
||||
}
|
||||
|
||||
artist.finalize()
|
||||
|
||||
unknownAlbums.removeAll(artistAlbums)
|
||||
}
|
||||
|
||||
|
@ -91,15 +90,15 @@ class MusicSorter(
|
|||
if (unknownAlbums.size > 0) {
|
||||
|
||||
// Reuse an existing unknown artist if one is found
|
||||
val unknownArtist = artists.find { it.name == "" } ?: Artist()
|
||||
val unknownArtist = Artist(
|
||||
name = artistPlaceholder
|
||||
)
|
||||
|
||||
for (album in unknownAlbums) {
|
||||
album.artist = unknownArtist
|
||||
unknownArtist.albums.add(album)
|
||||
}
|
||||
|
||||
unknownArtist.finalize()
|
||||
|
||||
artists.add(unknownArtist)
|
||||
|
||||
Log.d(
|
||||
|
@ -124,40 +123,39 @@ class MusicSorter(
|
|||
|
||||
// Then add them to the genre, along with refreshing the amount of artists
|
||||
genre.artists.addAll(genreArtists)
|
||||
genre.finalize()
|
||||
|
||||
unknownArtists.removeAll(genreArtists)
|
||||
}
|
||||
|
||||
if (unknownArtists.size > 0) {
|
||||
// Reuse an existing unknown genre if one is found
|
||||
val unknownGenre = genres.find { it.name == "" } ?: Genre()
|
||||
val unknownGenre = Genre(
|
||||
name = genrePlaceholder
|
||||
)
|
||||
|
||||
for (artist in unknownArtists) {
|
||||
artist.genres.add(unknownGenre)
|
||||
unknownGenre.artists.add(artist)
|
||||
}
|
||||
|
||||
unknownGenre.finalize()
|
||||
|
||||
genres.add(unknownGenre)
|
||||
|
||||
Log.d(
|
||||
this::class.simpleName,
|
||||
"${unknownArtists.size} albums were placed into an unknown genre."
|
||||
"${unknownArtists.size} artists were placed into an unknown genre."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Correct any empty names [""] with the proper placeholders [Unknown Album]
|
||||
private fun addPlaceholders() {
|
||||
genres.forEach { if (it.name == "") it.name = genrePlaceholder }
|
||||
artists.forEach { if (it.name == "") it.name = artistPlaceholder }
|
||||
albums.forEach { if (it.name == "") it.name = albumPlaceholder }
|
||||
}
|
||||
|
||||
// Sort all music
|
||||
// Finalize music
|
||||
private fun finalizeMusic() {
|
||||
// Correct any empty names [""] with the proper placeholders [Unknown Album]
|
||||
genres.forEach { it.finalize() }
|
||||
|
||||
artists.forEach { it.finalize() }
|
||||
|
||||
albums.forEach { it.finalize() }
|
||||
|
||||
// Then finally sort the music
|
||||
genres.sortWith(
|
||||
compareBy(String.CASE_INSENSITIVE_ORDER, { it.name })
|
||||
)
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
tools:text="2020" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/header_songs"
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/label_songs"
|
||||
|
@ -115,10 +115,8 @@
|
|||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/header_songs"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
app:layout_constraintTop_toBottomOf="@+id/header"
|
||||
tools:itemCount="4"
|
||||
tools:layout_editor_absoluteX="0dp"
|
||||
tools:listitem="@layout/item_album_song" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
tools:text="2 Albums, 20 Songs" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/header_albums"
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/label_albums"
|
||||
|
@ -103,8 +103,7 @@
|
|||
android:paddingTop="@dimen/padding_small"
|
||||
android:paddingBottom="@dimen/padding_small"
|
||||
android:background="@drawable/header_dividers"
|
||||
app:layout_constraintTop_toBottomOf="@+id/artist_counts"
|
||||
tools:layout_editor_absoluteX="60dp" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/album_recycler"
|
||||
|
@ -115,7 +114,7 @@
|
|||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/header_albums"
|
||||
app:layout_constraintTop_toBottomOf="@+id/header"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/item_album" />
|
||||
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
<string name="label_albums">Albums</string>
|
||||
<string name="label_songs">Songs</string>
|
||||
|
||||
<string name="placeholder_unknown_genre">Unknown Genre</string>
|
||||
<string name="placeholder_unknown_artist">Unknown Artist</string>
|
||||
<string name="placeholder_unknown_album">Unknown Album</string>
|
||||
<string name="placeholder_genre">Unknown Genre</string>
|
||||
<string name="placeholder_artist">Unknown Artist</string>
|
||||
<string name="placeholder_album">Unknown Album</string>
|
||||
|
||||
<string name="format_info">%1$s / %2$s</string>
|
||||
<string name="format_double_counts">%1$s, %2$s</string>
|
||||
|
|
Loading…
Reference in a new issue