diff --git a/app/build.gradle b/app/build.gradle index 5beefc70c..c054f21a4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,10 +47,18 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1' + // Navigation def navigation_version = "2.3.0" implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" implementation "androidx.navigation:navigation-ui-ktx:$navigation_version" + // Room Database + def room_version = "2.2.5" + kapt "androidx.room:room-compiler:$room_version" + implementation "androidx.room:room-runtime:$room_version" + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" + // Lifecycle implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" diff --git a/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt b/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt index 4e4253c92..24c1f731e 100644 --- a/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/loading/LoadingFragment.kt @@ -72,13 +72,13 @@ class LoadingFragment : Fragment() { // depending on which error response was given, along with a retry button binding.loadingBar.visibility = View.GONE - binding.statusText.visibility = View.VISIBLE + binding.errorText.visibility = View.VISIBLE binding.resetButton.visibility = View.VISIBLE if (response == MusicLoaderResponse.NO_MUSIC) { - binding.statusText.text = getString(R.string.error_no_music) + binding.errorText.text = getString(R.string.error_no_music) } else { - binding.statusText.text = getString(R.string.error_music_load_failed) + binding.errorText.text = getString(R.string.error_music_load_failed) } } @@ -89,7 +89,7 @@ class LoadingFragment : Fragment() { private fun onRetry(retry: Boolean) { if (retry) { binding.loadingBar.visibility = View.VISIBLE - binding.statusText.visibility = View.GONE + binding.errorText.visibility = View.GONE binding.resetButton.visibility = View.GONE loadingModel.doneWithRetry() diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index 8e36c028a..4b82c4bff 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -2,9 +2,7 @@ package org.oxycblt.auxio.music import android.app.Application import android.content.ContentResolver -import android.content.ContentUris import android.database.Cursor -import android.media.MediaMetadataRetriever import android.provider.MediaStore import android.provider.MediaStore.Audio.AudioColumns import android.util.Log @@ -15,12 +13,11 @@ enum class MusicLoaderResponse { } // Class that loads music from the FileSystem. -// This thing is probably full of memory leaks. +// FIXME: This thing probably has some memory leaks *somewhere* class MusicLoader(private val app: Application) { var songs = mutableListOf() - private val retriever: MediaMetadataRetriever = MediaMetadataRetriever() private var musicCursor: Cursor? = null val response: MusicLoaderResponse @@ -29,7 +26,7 @@ class MusicLoader(private val app: Application) { response = findMusic() } - private fun findMusic() : MusicLoaderResponse { + private fun findMusic(): MusicLoaderResponse { try { musicCursor = getCursor( app.contentResolver @@ -38,14 +35,11 @@ class MusicLoader(private val app: Application) { Log.i(this::class.simpleName, "Starting music search...") useCursor() - } catch (error: Exception) { - // TODO: Add better error handling - Log.e(this::class.simpleName, "Something went horribly wrong.") error.printStackTrace() - finalize() + musicCursor?.close() return MusicLoaderResponse.FAILURE } @@ -72,11 +66,20 @@ class MusicLoader(private val app: Application) { private fun getCursor(resolver: ContentResolver): Cursor? { Log.i(this::class.simpleName, "Getting music cursor.") + // Get all the values that can be retrieved by the cursor. + // The rest are retrieved using MediaMetadataExtractor and + // stored into a database. return resolver.query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, arrayOf( - AudioColumns._ID, - AudioColumns.DISPLAY_NAME + AudioColumns._ID, // 0 + AudioColumns.DISPLAY_NAME, // 1 + AudioColumns.TITLE, // 2 + AudioColumns.ARTIST, // 3 + AudioColumns.ALBUM, // 4 + AudioColumns.YEAR, // 5 + AudioColumns.TRACK, // 6 + AudioColumns.DURATION // 7 ), AudioColumns.IS_MUSIC + "=1", null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER @@ -94,81 +97,35 @@ class MusicLoader(private val app: Application) { val idIndex = cursor.getColumnIndexOrThrow(AudioColumns._ID) val displayIndex = cursor.getColumnIndexOrThrow(AudioColumns.DISPLAY_NAME) + val titleIndex = cursor.getColumnIndexOrThrow(AudioColumns.TITLE) + val artistIndex = cursor.getColumnIndexOrThrow(AudioColumns.ARTIST) + val albumIndex = cursor.getColumnIndexOrThrow(AudioColumns.ALBUM) + val yearIndex = cursor.getColumnIndexOrThrow(AudioColumns.YEAR) + val trackIndex = cursor.getColumnIndexOrThrow(AudioColumns.TRACK) + val durationIndex = cursor.getColumnIndexOrThrow(AudioColumns.DURATION) while (cursor.moveToNext()) { val id = cursor.getLong(idIndex) - // Read the current file from the ID - retriever.setDataSource( - app.applicationContext, - ContentUris.withAppendedId( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - id - ) - ) + // Get the basic metadata from the cursor + val title = cursor.getString(titleIndex) ?: cursor.getString(displayIndex) + val artist = cursor.getString(artistIndex) + val album = cursor.getString(albumIndex) + val year = cursor.getInt(yearIndex) + val track = cursor.getInt(trackIndex) + val duration = cursor.getLong(durationIndex) - // Get the metadata attributes - val title = retriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_TITLE - ) ?: cursor.getString(displayIndex) - - val artist = retriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_ARTIST - ) - - val album = retriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_ALBUM - ) - - val genre = retriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_GENRE - ) - - val year = ( - retriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_YEAR - ) ?: "0" - ).toInt() - - // Track is formatted as X/0, so trim off the /0 part to parse - // the track number correctly. - val track = ( - retriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER - ) ?: "0/0" - ).split("/")[0].toInt() - - // Something has gone horribly wrong if a file has no duration, - // so assert it as such. - val duration = retriever.extractMetadata( - MediaMetadataRetriever.METADATA_KEY_DURATION - )!!.toLong() - - // TODO: Add int-based genre compatibility + // TODO: Add album art [But its loaded separately, as that will take a bit] + // TODO: Add genres whenever android hasn't borked it songs.add( Song( - title, - artist, - album, - genre, - year, - track, - duration, - - retriever.embeddedPicture, - id - + id, title, artist, album, + year, track, duration ) ) } - } - } - // Free the metadata retriever & the musicCursor, and make the song list immutable. - private fun finalize() { - retriever.close() - musicCursor?.use{ - it.close() + cursor.close() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicSorting.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicSorting.kt index ea5c9a645..313ac96de 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicSorting.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicSorting.kt @@ -1,13 +1,11 @@ package org.oxycblt.auxio.music -import android.util.Log import org.oxycblt.auxio.music.models.Album import org.oxycblt.auxio.music.models.Artist import org.oxycblt.auxio.music.models.Song - // Sort a list of Song objects into lists of songs, albums, and artists. -fun processSongs(songs: MutableList) : MutableList { +fun processSongs(songs: MutableList): MutableList { // Eliminate all duplicates from the list // excluding the ID, as that's guaranteed to be unique [I think] return songs.distinctBy { @@ -16,7 +14,7 @@ fun processSongs(songs: MutableList) : MutableList { } // Sort a list of song objects into albums -fun sortIntoAlbums(songs: MutableList) : MutableList { +fun sortIntoAlbums(songs: MutableList): MutableList { val songsByAlbum = songs.groupBy { it.album } val albumList = mutableListOf() @@ -33,7 +31,7 @@ fun sortIntoAlbums(songs: MutableList) : MutableList { } // Sort a list of album objects into artists -fun sortIntoArtists(albums: MutableList) : MutableList { +fun sortIntoArtists(albums: MutableList): MutableList { val albumsByArtist = albums.groupBy { it.artist } val artistList = mutableListOf() @@ -48,4 +46,4 @@ fun sortIntoArtists(albums: MutableList) : MutableList { } return artistList -} \ No newline at end of file +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/models/Album.kt b/app/src/main/java/org/oxycblt/auxio/music/models/Album.kt index b8ef517c2..da6ce8811 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/models/Album.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/models/Album.kt @@ -1,20 +1,16 @@ package org.oxycblt.auxio.music.models -import android.graphics.Bitmap - // Abstraction for Song data class Album( var songs: List ) { var title: String? = null var artist: String? = null - var genre: String? = null - var cover: Bitmap? = null var year: Int = 0 init { // Iterate through the child songs and inherit the first valid value - // for the Album Name, Artist, Genre, Year, and Cover + // for the Album Name, Artist, and Year for (song in songs) { if (song.album != null) { title = song.album @@ -24,14 +20,6 @@ data class Album( artist = song.artist } - if (song.genre != null) { - genre = song.genre - } - - if (song.cover != null) { - cover = song.cover - } - if (song.year != 0) { year = song.year } diff --git a/app/src/main/java/org/oxycblt/auxio/music/models/Artist.kt b/app/src/main/java/org/oxycblt/auxio/music/models/Artist.kt index 237d5aa9d..498e8650b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/models/Artist.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/models/Artist.kt @@ -5,21 +5,13 @@ data class Artist( private var albums: List ) { var name: String? = null - var genre: String? = null - - // TODO: Artist photos init { - // Like Album, iterate through the child albums and pick out the first valid - // tag for Album/Genre + // Like Album, iterate through the child albums and pick out the first valid for artist for (album in albums) { if (album.artist != null) { name = album.artist } - - if (album.genre != null) { - genre = album.genre - } } // Also sort the mAlbums by year diff --git a/app/src/main/java/org/oxycblt/auxio/music/models/Song.kt b/app/src/main/java/org/oxycblt/auxio/music/models/Song.kt index 67335966b..74e40adab 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/models/Song.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/models/Song.kt @@ -1,32 +1,13 @@ package org.oxycblt.auxio.music.models -import android.graphics.Bitmap -import android.graphics.BitmapFactory - // Class containing all relevant values for a song. data class Song( + val id: Long, val name: String?, val artist: String?, val album: String?, - val genre: String?, val year: Int, val track: Int, val duration: Long, - - private val coverData: ByteArray?, - val id: Long -) { - var cover: Bitmap? = null - - init { - coverData?.let { data -> - // Decode the Album Cover ByteArray if not null. - val options = BitmapFactory.Options() - options.inMutable = true - - cover = BitmapFactory.decodeByteArray( - data, 0, data.size, options - ) - } - } -} + val coverData: ByteArray? = null +) diff --git a/app/src/main/res/layout/fragment_loading.xml b/app/src/main/res/layout/fragment_loading.xml index 3849e01de..b0456f858 100644 --- a/app/src/main/res/layout/fragment_loading.xml +++ b/app/src/main/res/layout/fragment_loading.xml @@ -21,7 +21,7 @@ android:indeterminateTint="?attr/colorAccent" android:indeterminateTintMode="src_in" android:paddingBottom="@dimen/padding_small" - app:layout_constraintBottom_toTopOf="@+id/status_text" + app:layout_constraintBottom_toTopOf="@+id/error_text" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" @@ -29,15 +29,15 @@ app:layout_constraintVertical_chainStyle="packed" /> + app:layout_constraintTop_toBottomOf="@+id/loading_bar" + tools:text="@string/error_music_load_failed" />