Add music sorting

Change MusicRepository so that all music is sorted into Artist/Album/Song lists.
This commit is contained in:
OxygenCobalt 2020-08-18 10:37:02 -06:00
parent 22e8049d6b
commit a8b368b577
11 changed files with 180 additions and 23 deletions

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.oxycblt.auxio"> package="org.oxycblt.auxio">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application <application

View file

@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentLibraryBinding import org.oxycblt.auxio.databinding.FragmentLibraryBinding
import org.oxycblt.auxio.music.MusicRepository
class LibraryFragment : Fragment() { class LibraryFragment : Fragment() {
@ -28,6 +29,8 @@ class LibraryFragment : Fragment() {
inflater, R.layout.fragment_library, container, false inflater, R.layout.fragment_library, container, false
) )
MusicRepository.getInstance().init(requireActivity().application)
Log.d(this::class.simpleName, "Fragment created.") Log.d(this::class.simpleName, "Fragment created.")
return binding.root return binding.root

View file

@ -8,7 +8,6 @@ class LibraryViewModel() : ViewModel() {
// TODO: Implement music data in ViewModel // TODO: Implement music data in ViewModel
init { init {
Log.d(this::class.simpleName, "ViewModel created.") Log.d(this::class.simpleName, "ViewModel created.")
} }
} }

View file

@ -0,0 +1,46 @@
package org.oxycblt.auxio.music
// Basic Abstraction for Song
data class Album (
var mSongs: List<Song>
) {
private var mTitle: String? = null
private var mArtist: String? = null
//private var mGenre: String? = null
private var mYear: Int = 0
// Immutable backings as the member variables are mutable
val title: String? get() = mTitle
val artist: String? get() = mArtist
//val genre: String? get() = genre
val year: Int get() = mYear
val songs: List<Song> get() = mSongs
init {
// Iterate through the child songs and inherit the first valid value
// for the Album name & year, otherwise it will revert to its defaults
for (song in mSongs) {
if (song.album != null) {
mTitle = song.album
}
if (song.artist != null) {
mArtist = song.artist
}
/*
if (song.genre != null) {
mGenre = song.genre
}
*/
if (song.year != 0) {
mYear = song.year
}
}
// Also sort the songs by track
mSongs = songs.sortedBy { it.track }
}
}

View file

@ -0,0 +1,34 @@
package org.oxycblt.auxio.music
// Abstraction for mAlbums
data class Artist(
private var mAlbums: List<Album>
) {
private var mName: String? = null
//private var mGenre: String? = null
// Immutable backings as the member variables are mutable
val name: String? get() = mName
//val genre: String? get() = mGenre
val albums: List<Album> get() = mAlbums
init {
// Like album, iterate through the child albums and pick out the first valid
// tag for Album/Genre
for (album in mAlbums) {
if (album.artist != null) {
mName = album.artist
}
/*
if (album.genre != null) {
mGenre = album.genre
}
*/
}
// Also sort the mAlbums by year
mAlbums = mAlbums.sortedBy { it.year }
}
}

View file

@ -9,21 +9,34 @@ import android.util.Log
// Storage for music data. Design largely adapted from Music Player GO: // Storage for music data. Design largely adapted from Music Player GO:
// https://github.com/enricocid/Music-Player-GO // https://github.com/enricocid/Music-Player-GO
class MusicRepository() { class MusicRepository {
var rawMusicList = mutableListOf<RawMusic>() private lateinit var mArtists: List<Artist>
private lateinit var mAlbums: List<Album>
private lateinit var mSongs: List<Song>
fun getMusic(app: Application): MutableList<RawMusic> { // Not sure if backings are necessary but they're vars so better safe than sorry
findMusic(app)?.let { rm -> val artists: List<Artist> get() = mArtists
val albums: List<Album> get() = mAlbums
val songs: List<Song> get() = mSongs
// TODO: Sort the raw music fun init(app: Application): Boolean {
rawMusicList = rm findMusic(app)?.let { ss ->
if (ss.size > 0) {
processSongs(ss)
return true
}
} }
return rawMusicList // Return false if the load as failed for any reason, either
// through there being no music or an Exception.
return false
} }
private fun findMusic(app: Application): MutableList<RawMusic>? { private fun findMusic(app: Application): MutableList<Song>? {
val songList = mutableListOf<Song>()
try { try {
val musicCursor = getCursor( val musicCursor = getCursor(
@ -42,8 +55,8 @@ class MusicRepository() {
val idIndex = cursor.getColumnIndexOrThrow(AudioColumns._ID) val idIndex = cursor.getColumnIndexOrThrow(AudioColumns._ID)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
rawMusicList.add( songList.add(
RawMusic( Song(
cursor.getString(nameIndex), cursor.getString(nameIndex),
cursor.getString(artistIndex), cursor.getString(artistIndex),
cursor.getString(albumIndex), cursor.getString(albumIndex),
@ -58,10 +71,10 @@ class MusicRepository() {
Log.d( Log.d(
this::class.simpleName, this::class.simpleName,
"Music search ended with " + rawMusicList.size.toString() + " Songs found." "Music search finished with " + songList.size.toString() + " Songs found."
) )
return rawMusicList return songList
} catch (error: Exception) { } catch (error: Exception) {
// TODO: Add better error handling // TODO: Add better error handling
@ -89,6 +102,61 @@ class MusicRepository() {
AudioColumns.IS_MUSIC + "=1", null, AudioColumns.IS_MUSIC + "=1", null,
MediaStore.Audio.Media.DEFAULT_SORT_ORDER MediaStore.Audio.Media.DEFAULT_SORT_ORDER
) )
// TODO: Art Loading, since android cant do it on its own
// TODO: Genre Loading?
}
// Sort the list of Song objects into an abstracted lis
private fun processSongs(songs: MutableList<Song>) {
// Eliminate all duplicates from the list
// excluding the ID, as that's guaranteed to be unique [I think]
val distinctSongs = songs.distinctBy {
it.name to it.artist to it.album to it.year to it.track to it.duration
}.toMutableList()
// Sort the music by artists/albums
val songsByAlbum = distinctSongs.groupBy { it.album }
val albumList = mutableListOf<Album>()
songsByAlbum.keys.iterator().forEach { album ->
val albumSongs = songsByAlbum[album]
// Add an album abstraction for each album item in the list of songs.
albumSongs?.let {
albumList.add(
Album(albumSongs)
)
}
}
// Then abstract the remaining albums into artist objects
// TODO: If enabled
val albumsByArtist = albumList.groupBy { it.artist }
val artistList = mutableListOf<Artist>()
albumsByArtist.keys.iterator().forEach { artist ->
val artistAlbums = albumsByArtist[artist]
artistAlbums?.let {
artistList.add(
Artist(artistAlbums)
)
}
}
Log.i(this::class.simpleName,
"Successfully sorted songs into "
+ artistList.size.toString()
+ " Artists and "
+ albumList.size.toString()
+ " Albums."
)
mArtists = artistList
mAlbums = albumList
mSongs = distinctSongs
} }
companion object { companion object {
@ -99,6 +167,11 @@ class MusicRepository() {
val tempInstance = INSTANCE val tempInstance = INSTANCE
if (tempInstance != null) { if (tempInstance != null) {
Log.d(
this::class.simpleName,
"Passed an existing instance of MusicRepository."
)
return tempInstance return tempInstance
} }
@ -107,7 +180,7 @@ class MusicRepository() {
INSTANCE = newInstance INSTANCE = newInstance
Log.d( Log.d(
MusicRepository::class.simpleName, this::class.simpleName,
"Created an instance of MusicRepository." "Created an instance of MusicRepository."
) )

View file

@ -1,13 +1,14 @@
package org.oxycblt.auxio.music package org.oxycblt.auxio.music
// Class containing all relevant values for a song // Class containing all relevant values for a song.
// TODO: Is broken into Artist, Album, and Song classes data class Song(
data class RawMusic(
val name: String?, val name: String?,
val artist: String?, val artist: String?,
val album: String?, val album: String?,
//val genre: String?,
val year: Int, val year: Int,
val track: Int, val track: Int,
val duration: Long, val duration: Long,
val id: Long? val id: Long?
) )

View file

@ -13,7 +13,6 @@
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:elevation="@dimen/elevation_normal" android:elevation="@dimen/elevation_normal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:title="@string/fragment_library_title" app:title="@string/fragment_library_title"
tools:titleTextColor="@color/primaryTextColor" /> tools:titleTextColor="@color/primaryTextColor" />

View file

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android" <navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_main" android:id="@+id/nav_main"
app:startDestination="@id/playerFragment"> app:startDestination="@id/playerFragment">
<fragment <fragment
android:id="@+id/playerFragment" android:id="@+id/playerFragment"
android:name="org.oxycblt.auxio.library.LibraryFragment" android:name="org.oxycblt.auxio.library.LibraryFragment"
android:label="PlayerFragment" /> android:label="PlayerFragment"
tools:layout="@layout/fragment_library" />
</navigation> </navigation>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="primaryColor">#272727</color> <color name="primaryColor">#424242</color>
<color name="primaryLightColor">#6d6d6d</color> <color name="primaryLightColor">#6d6d6d</color>
<color name="primaryDarkColor">#1b1b1b</color> <color name="primaryDarkColor">#1b1b1b</color>
<color name="secondaryColor">#212121</color> <color name="secondaryColor">#212121</color>

View file

@ -3,7 +3,7 @@
<!-- Base theme --> <!-- Base theme -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar"> <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="colorPrimary">@color/primaryColor</item> <item name="colorPrimary">@color/primaryColor</item>
<item name="colorPrimaryDark">@color/primaryColor</item> <item name="colorPrimaryDark">@color/primaryDarkColor</item>
<item name="colorAccent">@color/primaryColor</item> <item name="colorAccent">@color/secondaryColor</item>
</style> </style>
</resources> </resources>