Update Music Loading
Slightly tweak the music models, use plural strings when formatting args, and properly sort the music items after everything else is sorted.
This commit is contained in:
parent
565d1efa96
commit
016d664e51
16 changed files with 68 additions and 79 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,6 +2,7 @@
|
|||
.gradle
|
||||
local.properties
|
||||
build/
|
||||
release/
|
||||
|
||||
# Studio
|
||||
.idea/
|
||||
|
|
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
|
@ -18,4 +18,6 @@
|
|||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-dontobfuscate
|
|
@ -11,6 +11,8 @@ import org.oxycblt.auxio.theme.accent
|
|||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
// TODO: Collapse LoadingFragment/MainFragment into MainActivity.
|
||||
|
||||
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
|
||||
// Debugging placeholder
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||
|
|
|
@ -21,8 +21,6 @@ import org.oxycblt.auxio.music.processing.MusicLoaderResponse
|
|||
|
||||
class LoadingFragment : Fragment(R.layout.fragment_loading) {
|
||||
|
||||
// TODO: Phase out LoadingFragment
|
||||
|
||||
private val loadingModel: LoadingViewModel by lazy {
|
||||
ViewModelProvider(
|
||||
this,
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.oxycblt.auxio.music
|
|||
import android.content.ContentUris
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.databinding.BindingAdapter
|
||||
|
@ -65,57 +66,40 @@ fun Long.toAlbumArtURI(): Uri {
|
|||
)
|
||||
}
|
||||
|
||||
fun Int.toSongCount(): Int {
|
||||
return if (this < 2) {
|
||||
R.string.label_single_song
|
||||
} else {
|
||||
R.string.format_multi_song_count
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.toAlbumCount(): Int {
|
||||
return if (this < 2) {
|
||||
R.string.label_single_album
|
||||
} else {
|
||||
R.string.format_album_count
|
||||
}
|
||||
}
|
||||
|
||||
// Format the amount of songs in an album
|
||||
@BindingAdapter("songCount")
|
||||
fun TextView.getAlbumSongs(album: Album) {
|
||||
text = context.getString(album.numSongs.toSongCount(), album.numSongs)
|
||||
text = context.resources.getQuantityString(R.plurals.format_song_count, album.numSongs)
|
||||
}
|
||||
|
||||
@BindingAdapter("albumSongCount")
|
||||
fun TextView.getSongAlbumCount(artist: Artist) {
|
||||
val albums = context.getString(artist.numAlbums.toAlbumCount(), artist.numAlbums)
|
||||
val songs = context.getString(artist.numSongs.toSongCount(), artist.numSongs)
|
||||
@BindingAdapter("artistCounts")
|
||||
fun TextView.getArtistCounts(artist: Artist) {
|
||||
// Get the quantity string for both albums & artists, and then stitch them together.
|
||||
val albums = context.resources.getQuantityString(
|
||||
R.plurals.format_albums, artist.numAlbums, artist.numAlbums
|
||||
)
|
||||
val songs = context.resources.getQuantityString(
|
||||
R.plurals.format_song_count, artist.numSongs, artist.numSongs
|
||||
)
|
||||
|
||||
text = context.getString(R.string.format_combined_song_album, albums, songs)
|
||||
Log.d("getArtistCounts", albums)
|
||||
|
||||
text = context.getString(R.string.format_double_counts, albums, songs)
|
||||
}
|
||||
|
||||
// Get the cover art
|
||||
@BindingAdapter("coverArt")
|
||||
fun ImageView.getCoverArt(any: Any) {
|
||||
val uri = when (any) {
|
||||
is Song -> any.album.coverUri
|
||||
is Album -> any.coverUri
|
||||
|
||||
else -> Uri.EMPTY
|
||||
}
|
||||
|
||||
load(uri) {
|
||||
fun ImageView.getCoverArt(song: Song) {
|
||||
load(song.album.coverUri) {
|
||||
crossfade(true)
|
||||
placeholder(android.R.color.transparent)
|
||||
error(R.drawable.ic_music)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the artist image.
|
||||
@BindingAdapter("artistImage")
|
||||
fun ImageView.getArtistImage(artist: Artist) {
|
||||
load(artist.albums[0].coverUri) {
|
||||
@BindingAdapter("coverArt")
|
||||
fun ImageView.getCoverArt(album: Album) {
|
||||
load(album.coverUri) {
|
||||
crossfade(true)
|
||||
placeholder(android.R.color.transparent)
|
||||
error(R.drawable.ic_music)
|
||||
|
|
|
@ -5,15 +5,15 @@ import android.net.Uri
|
|||
// Abstraction for Song
|
||||
data class Album(
|
||||
val id: Long = 0L,
|
||||
var title: String = "",
|
||||
val artistName: String = "", // Only used for sorting. Use artist for everything else.
|
||||
var name: String = "",
|
||||
val artistName: String = "", // only used for sorting. Use artist.name instead.
|
||||
val coverUri: Uri = Uri.EMPTY,
|
||||
val year: Int = 0,
|
||||
var numSongs: Int = 0
|
||||
val year: Int = 0
|
||||
) {
|
||||
lateinit var artist: Artist
|
||||
|
||||
val songs = mutableListOf<Song>()
|
||||
val numSongs: Int get() = songs.size
|
||||
|
||||
fun finalize() {
|
||||
songs.sortBy { it.track }
|
||||
|
|
|
@ -7,13 +7,13 @@ data class Artist(
|
|||
val genres: MutableList<Genre> = mutableListOf(Genre())
|
||||
) {
|
||||
val albums = mutableListOf<Album>()
|
||||
var numAlbums = 0
|
||||
|
||||
val numAlbums: Int get() = albums.size
|
||||
var numSongs = 0
|
||||
|
||||
fun finalize() {
|
||||
albums.sortByDescending { it.year }
|
||||
|
||||
numAlbums = albums.size
|
||||
albums.forEach { album ->
|
||||
numSongs += album.numSongs
|
||||
}
|
||||
|
|
|
@ -5,10 +5,8 @@ data class Genre(
|
|||
var name: String = "",
|
||||
) {
|
||||
val artists = mutableListOf<Artist>()
|
||||
var numArtists = 0
|
||||
|
||||
fun finalize() {
|
||||
artists.sortByDescending { it.name }
|
||||
numArtists = artists.size
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import android.text.format.DateUtils
|
|||
// Class containing all relevant values for a song.
|
||||
data class Song(
|
||||
val id: Long,
|
||||
var title: String,
|
||||
val albumName: String, // Only used for sorting. Use artist for everything else.
|
||||
var name: String,
|
||||
val albumName: String, // Only used for sorting. Use album.title for everything else.
|
||||
val track: Int,
|
||||
val duration: Long
|
||||
) {
|
||||
|
|
|
@ -113,8 +113,8 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
artistCursor = resolver.query(
|
||||
Genres.Members.getContentUri("external", genre.id),
|
||||
arrayOf(
|
||||
Artists._ID,
|
||||
Artists.ARTIST
|
||||
Artists._ID, // 0
|
||||
Artists.ARTIST // 1
|
||||
),
|
||||
null, null,
|
||||
Artists.DEFAULT_SORT_ORDER
|
||||
|
@ -171,7 +171,6 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
Albums.ARTIST, // 2
|
||||
|
||||
Albums.FIRST_YEAR, // 3
|
||||
Albums.NUMBER_OF_SONGS // 4
|
||||
),
|
||||
null, null,
|
||||
Albums.DEFAULT_SORT_ORDER
|
||||
|
@ -182,21 +181,19 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
val nameIndex = cursor.getColumnIndexOrThrow(Albums.ALBUM)
|
||||
val artistIndex = cursor.getColumnIndexOrThrow(Albums.ARTIST)
|
||||
val yearIndex = cursor.getColumnIndexOrThrow(Albums.FIRST_YEAR)
|
||||
val numIndex = cursor.getColumnIndexOrThrow(Albums.NUMBER_OF_SONGS)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idIndex)
|
||||
val name = cursor.getString(nameIndex) ?: ""
|
||||
val artist = cursor.getString(artistIndex) ?: ""
|
||||
val year = cursor.getInt(yearIndex)
|
||||
val numSongs = cursor.getInt(numIndex)
|
||||
|
||||
val coverUri = id.toAlbumArtURI()
|
||||
|
||||
albums.add(
|
||||
Album(
|
||||
id, name, artist,
|
||||
coverUri, year, numSongs
|
||||
coverUri, year
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -206,7 +203,7 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
|
||||
// Remove dupes
|
||||
albums = albums.distinctBy {
|
||||
it.title to it.artistName to it.year to it.numSongs
|
||||
it.name to it.artistName to it.year to it.numSongs
|
||||
}.toMutableList()
|
||||
|
||||
Log.d(
|
||||
|
@ -260,7 +257,7 @@ class MusicLoader(private val resolver: ContentResolver) {
|
|||
|
||||
// Remove dupes
|
||||
songs = songs.distinctBy {
|
||||
it.title to it.albumName to it.track to it.duration
|
||||
it.name to it.albumName to it.track to it.duration
|
||||
}.toMutableList()
|
||||
|
||||
Log.d(
|
||||
|
|
|
@ -22,6 +22,7 @@ class MusicSorter(
|
|||
sortArtistsIntoGenres()
|
||||
|
||||
addPlaceholders()
|
||||
finalizeMusic()
|
||||
}
|
||||
|
||||
private fun sortSongsIntoAlbums() {
|
||||
|
@ -31,7 +32,7 @@ class MusicSorter(
|
|||
|
||||
for (album in albums) {
|
||||
// Find all songs that match the current album title
|
||||
val albumSongs = songs.filter { it.albumName == album.title }
|
||||
val albumSongs = songs.filter { it.albumName == album.name }
|
||||
|
||||
// Then add them to the album
|
||||
for (song in albumSongs) {
|
||||
|
@ -46,14 +47,13 @@ class MusicSorter(
|
|||
if (unknownSongs.size > 0) {
|
||||
|
||||
// Reuse an existing unknown album if one is found
|
||||
val unknownAlbum = albums.find { it.title == "" } ?: Album()
|
||||
val unknownAlbum = albums.find { it.name == "" } ?: Album()
|
||||
|
||||
for (song in unknownSongs) {
|
||||
song.album = unknownAlbum
|
||||
unknownAlbum.songs.add(song)
|
||||
}
|
||||
|
||||
unknownAlbum.numSongs = unknownAlbum.songs.size
|
||||
unknownAlbum.finalize()
|
||||
|
||||
albums.add(unknownAlbum)
|
||||
|
@ -63,8 +63,6 @@ class MusicSorter(
|
|||
"${unknownSongs.size} songs were placed into an unknown album."
|
||||
)
|
||||
}
|
||||
|
||||
albums.sortByDescending { it.title }
|
||||
}
|
||||
|
||||
private fun sortAlbumsIntoArtists() {
|
||||
|
@ -107,8 +105,6 @@ class MusicSorter(
|
|||
"${unknownAlbums.size} albums were placed into an unknown artist."
|
||||
)
|
||||
}
|
||||
|
||||
artists.sortByDescending { it.name }
|
||||
}
|
||||
|
||||
private fun sortArtistsIntoGenres() {
|
||||
|
@ -126,7 +122,7 @@ class MusicSorter(
|
|||
|
||||
// Then add them to the genre, along with refreshing the amount of artists
|
||||
genre.artists.addAll(genreArtists)
|
||||
genre.numArtists = artists.size
|
||||
genre.finalize()
|
||||
|
||||
unknownArtists.removeAll(genreArtists)
|
||||
}
|
||||
|
@ -140,7 +136,7 @@ class MusicSorter(
|
|||
unknownGenre.artists.add(artist)
|
||||
}
|
||||
|
||||
unknownGenre.numArtists = artists.size
|
||||
unknownGenre.finalize()
|
||||
|
||||
genres.add(unknownGenre)
|
||||
|
||||
|
@ -149,14 +145,19 @@ class MusicSorter(
|
|||
"${unknownArtists.size} albums were placed into an unknown genre."
|
||||
)
|
||||
}
|
||||
|
||||
genres.sortByDescending { it.name }
|
||||
}
|
||||
|
||||
// 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.title == "") it.title = albumPlaceholder }
|
||||
albums.forEach { if (it.name == "") it.name = albumPlaceholder }
|
||||
}
|
||||
|
||||
// Sort all music into
|
||||
private fun finalizeMusic() {
|
||||
genres.sortBy { it.name }
|
||||
artists.sortBy { it.name }
|
||||
albums.sortBy { it.name }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class SongsFragment : Fragment() {
|
|||
binding.songRecycler.adapter = SongAdapter(
|
||||
songsModel.songs.value!!,
|
||||
ClickListener { song ->
|
||||
Log.d(this::class.simpleName, song.title)
|
||||
Log.d(this::class.simpleName, song.name)
|
||||
}
|
||||
)
|
||||
binding.songRecycler.applyDivider()
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:text="@{album.title}"
|
||||
android:text="@{album.name}"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:ellipsize="end"
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:albumSongCount="@{artist}"
|
||||
app:artistCounts="@{artist}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/artist_name"
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{song.title}"
|
||||
android:text="@{song.name}"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/song_info"
|
||||
|
@ -55,7 +55,7 @@
|
|||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="@{@string/format_song_info(song.album.artist.name, song.album.title)}"
|
||||
android:text="@{@string/format_song_info(song.album.artist.name, song.album.name)}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/duration"
|
||||
app:layout_constraintStart_toEndOf="@+id/cover"
|
||||
|
|
|
@ -11,15 +11,21 @@
|
|||
|
||||
<string name="label_retry">Retry</string>
|
||||
<string name="label_grant">Grant</string>
|
||||
<string name="label_single_song">1 Song</string>
|
||||
<string name="label_single_album">1 Album</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="format_multi_song_count">%s Songs</string>
|
||||
<string name="format_song_info">%s / %s</string>
|
||||
<string name="format_album_count">%s Albums</string>
|
||||
<string name="format_combined_song_album">%s, %s</string>
|
||||
<string name="format_song_info">%1$s / %2$s</string>
|
||||
<string name="format_double_counts">%1$s, %2$s</string>
|
||||
|
||||
<plurals name="format_song_count">
|
||||
<item quantity="one">%s Song</item>
|
||||
<item quantity="other">%s Songs</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="format_albums">
|
||||
<item quantity="one">%s Album</item>
|
||||
<item quantity="other">%s Albums</item>
|
||||
</plurals>
|
||||
</resources>
|
Loading…
Reference in a new issue