diff --git a/app/build.gradle b/app/build.gradle index 2b5bb7b73..e63f5a932 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,6 +28,7 @@ android { applicationIdSuffix = '.debug' versionNameSuffix = "-DEBUG" } + release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' @@ -62,7 +63,7 @@ dependencies { // General implementation 'androidx.core:core-ktx:1.3.2' - implementation 'androidx.activity:activity-ktx:1.3.0-alpha02' + implementation 'androidx.activity:activity-ktx:1.3.0-alpha03' implementation 'androidx.fragment:fragment-ktx:1.3.0' // Layout diff --git a/app/src/main/java/org/oxycblt/auxio/music/Models.kt b/app/src/main/java/org/oxycblt/auxio/music/Models.kt index 6712f332c..a7604234f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Models.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Models.kt @@ -26,11 +26,13 @@ sealed class Parent : BaseModel() { /** * The data object for a song. Inherits [BaseModel]. * @property fileName The raw filename for this track - * @property albumId The Song's Album ID. Never use this outside of when attaching a song to its album. + * @property albumId The Song's Album ID. + * Never use this outside of when attaching a song to its album. * @property track The Song's Track number * @property duration The duration of the song, in millis. * @property album The Song's parent album. Use this instead of [albumId]. - * @property genre The Song's [Genre] + * @property genre The Song's [Genre]. + * These are not ensured to be linked due to possible quirks in the genre loading system. * @property seconds The Song's duration in seconds * @property formattedDuration The Song's duration as a duration string. */ @@ -86,7 +88,8 @@ data class Album( private val mSongs = mutableListOf() val songs: List get() = mSongs - val totalDuration: String get() = songs.sumOf { it.seconds }.toDuration() + val totalDuration: String get() = + songs.sumOf { it.seconds }.toDuration() fun linkArtist(artist: Artist) { mArtist = artist @@ -118,6 +121,8 @@ data class Artist( } val genre: Genre? by lazy { + // Get the genre that corresponds to the most songs in this artist, which would be + // the most "Prominent" genre. songs.groupBy { it.genre }.entries.maxByOrNull { it.value.size }?.key } @@ -135,18 +140,15 @@ data class Genre( override val id: Long = -1, override val name: String, ) : Parent() { - val resolvedName: String by lazy { - if (name.contains(Regex("([1-9])"))) { - name.toNamedGenre() ?: name - } else { - name - } - } - private val mSongs = mutableListOf() val songs: List get() = mSongs - val totalDuration: String get() = songs.sumOf { it.seconds }.toDuration() + val resolvedName: String by lazy { + name.getGenreNameCompat() ?: name + } + + val totalDuration: String get() = + songs.sumOf { it.seconds }.toDuration() fun linkSong(song: Song) { mSongs.add(song) 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 3b3ef7a5d..2703be550 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -191,8 +191,9 @@ class MusicLoader(private val context: Context) { artists.add( // IDs are incremented from the minimum int value so that they remain unique. Artist( - id = (artists.size + Int.MIN_VALUE).toLong(), - name = entry.key, albums = entry.value + id = (Int.MIN_VALUE + artists.size).toLong(), + name = entry.key, + albums = entry.value ) ) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt index c889cdb7e..0686f008a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -84,9 +84,7 @@ class MusicStore private constructor() { * @return The corresponding [Song] for this [uri], null if there isnt one. */ fun getSongForUri(uri: Uri, resolver: ContentResolver): Song? { - resolver.query( - uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null - )?.use { cursor -> + resolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor -> cursor.moveToFirst() val fileName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt index 6a2efa22a..18501a4f0 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt @@ -7,6 +7,7 @@ import android.provider.MediaStore import android.text.format.DateUtils import android.widget.ImageButton import android.widget.TextView +import androidx.core.text.isDigitsOnly import androidx.databinding.BindingAdapter import org.oxycblt.auxio.R import org.oxycblt.auxio.recycler.SortMode @@ -17,6 +18,7 @@ import org.oxycblt.auxio.ui.getPlural * There are a lot more int-genre extensions as far as Im aware, but this works for most cases. */ private val ID3_GENRES = arrayOf( + // ID3 Standard "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", @@ -28,6 +30,7 @@ private val ID3_GENRES = arrayOf( "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", + // Winamp Extensions "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", @@ -37,26 +40,41 @@ private val ID3_GENRES = arrayOf( "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "Britpop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", - "Anime", "JPop", "Synthpop" -) + "Anime", "JPop", "Synthpop", -private const val PAREN_FILTER = "()" + // Winamp 5.6+ extensions, used by EasyTAG and friends + "Abstract", "Art Rock", "Baroque", "Bhangra", "Big Beat", "Breakbeat", "Chillout", "Downtempo", + "Dub", "EBM", "Eclectic", "Electro", "Electroclash", "Emo", "Experimental", "Garage", "Global", + "IDM", "Illbient", "Industro-Goth", "Jam Band", "Krautrock", "Leftfield", "Lounge", "Math Rock", // S I X T Y F I V E + "New Romantic", "Nu-Breakz", "Post-Punk", "Post-Rock", "Psytrance", "Shoegaze", "Space Rock", + "Trop Rock", "World Music", "Neoclassical", "Audiobook", "Audio Theatre", "Neue Deutsche Welle", + "Podcast", "Indie Rock", "G-Funk", "Dubstep", "Garage Rock", "Psybient" +) // --- EXTENSION FUNCTIONS --- /** - * Convert legacy ID3 genres to their named genre - * @return The named genre for this legacy genre. + * Convert legacy int-based ID3 genres to their human-readable genre + * @return The named genre for this legacy genre, null if there is no need to parse it or if the genre is invalid. */ -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() +fun String.getGenreNameCompat(): String? { + if (isDigitsOnly()) { + // ID3 v1, just parse as an integer + return ID3_GENRES.getOrNull(toInt()) + } - // If the conversion fails [Due to the genre using an extension that Auxio doesn't have], - // then return null. - return ID3_GENRES.getOrNull(intGenre) + if (startsWith('(') && endsWith(')')) { + // ID3 v2+, parse out the parentheses and get the integer + // Any genres formatted as "(CHARS)" will be ignored. + val genreInt = substring(1 until lastIndex).toIntOrNull() + + if (genreInt != null) { + return ID3_GENRES.getOrNull(genreInt) + } + } + + // ID3 v3+, current name is fine. + return null } /** @@ -92,7 +110,7 @@ fun Long.toDuration(): String { */ fun Int.toYear(context: Context): String { return if (this > 0) { - this.toString() + toString() } else { context.getString(R.string.placeholder_no_date) }