Compare commits
4 commits
28c0872676
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| bdbea9fb42 | |||
| 4648e56b15 | |||
| 13ea8dd04d | |||
| 0c7204ccfb |
30 changed files with 603 additions and 461 deletions
|
|
@ -14,7 +14,7 @@ android {
|
||||||
renderscriptTargetApi 29//must match target sdk and build tools
|
renderscriptTargetApi 29//must match target sdk and build tools
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
applicationId "code.name.monkey.retromusic"
|
applicationId "software.lavender.music"
|
||||||
versionCode 10545
|
versionCode 10545
|
||||||
versionName '5.4.2 ' + "_" + getDate()
|
versionName '5.4.2 ' + "_" + getDate()
|
||||||
|
|
||||||
|
|
@ -161,6 +161,8 @@ dependencies {
|
||||||
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
|
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
|
||||||
implementation 'cat.ereza:customactivityoncrash:2.3.0'
|
implementation 'cat.ereza:customactivityoncrash:2.3.0'
|
||||||
debugImplementation 'com.github.amitshekhariitbhu:Android-Debug-Database:1.0.6'
|
debugImplementation 'com.github.amitshekhariitbhu:Android-Debug-Database:1.0.6'
|
||||||
|
|
||||||
|
implementation 'com.google.android.exoplayer:exoplayer-core:2.16.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: '../spotless.gradle'
|
apply from: '../spotless.gradle'
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,6 @@ class App : Application() {
|
||||||
return instance!!
|
return instance!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isProVersion(): Boolean {
|
fun isProVersion() = true
|
||||||
return BuildConfig.DEBUG || instance?.billingProcessor!!.isPurchased(
|
|
||||||
PRO_VERSION_PRODUCT_ID
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@ class HistoryEntity(
|
||||||
@ColumnInfo(name = "artist_name")
|
@ColumnInfo(name = "artist_name")
|
||||||
val artistName: String,
|
val artistName: String,
|
||||||
val composer: String?,
|
val composer: String?,
|
||||||
|
@ColumnInfo(name = "cover_art")
|
||||||
|
val coverArt: String?,
|
||||||
|
val genres: String?,
|
||||||
@ColumnInfo(name = "album_artist")
|
@ColumnInfo(name = "album_artist")
|
||||||
val albumArtist: String?,
|
val albumArtist: String?,
|
||||||
@ColumnInfo(name = "time_played")
|
@ColumnInfo(name = "time_played")
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,9 @@ class PlayCountEntity(
|
||||||
val composer: String?,
|
val composer: String?,
|
||||||
@ColumnInfo(name = "album_artist")
|
@ColumnInfo(name = "album_artist")
|
||||||
val albumArtist: String?,
|
val albumArtist: String?,
|
||||||
|
@ColumnInfo(name = "cover_art")
|
||||||
|
val coverArt: String?,
|
||||||
|
val genres: String?,
|
||||||
@ColumnInfo(name = "time_played")
|
@ColumnInfo(name = "time_played")
|
||||||
val timePlayed: Long,
|
val timePlayed: Long,
|
||||||
@ColumnInfo(name = "play_count")
|
@ColumnInfo(name = "play_count")
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import androidx.room.RoomDatabase
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [PlaylistEntity::class, SongEntity::class, HistoryEntity::class, PlayCountEntity::class, BlackListStoreEntity::class, LyricsEntity::class],
|
entities = [PlaylistEntity::class, SongEntity::class, HistoryEntity::class, PlayCountEntity::class, BlackListStoreEntity::class, LyricsEntity::class],
|
||||||
version = 23,
|
version = 24,
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
)
|
)
|
||||||
abstract class RetroDatabase : RoomDatabase() {
|
abstract class RetroDatabase : RoomDatabase() {
|
||||||
|
|
|
||||||
|
|
@ -48,5 +48,8 @@ class SongEntity(
|
||||||
val artistName: String,
|
val artistName: String,
|
||||||
val composer: String?,
|
val composer: String?,
|
||||||
@ColumnInfo(name = "album_artist")
|
@ColumnInfo(name = "album_artist")
|
||||||
val albumArtist: String?
|
val albumArtist: String?,
|
||||||
|
@ColumnInfo(name = "cover_art")
|
||||||
|
val coverArt: String?,
|
||||||
|
val genres: String?
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,9 @@ fun Song.toHistoryEntity(timePlayed: Long): HistoryEntity {
|
||||||
artistName = artistName,
|
artistName = artistName,
|
||||||
composer = composer,
|
composer = composer,
|
||||||
albumArtist = albumArtist,
|
albumArtist = albumArtist,
|
||||||
timePlayed = timePlayed
|
timePlayed = timePlayed,
|
||||||
|
coverArt = coverArt,
|
||||||
|
genres = genres?.joinToString(";")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,7 +64,9 @@ fun Song.toSongEntity(playListId: Long): SongEntity {
|
||||||
artistId = artistId,
|
artistId = artistId,
|
||||||
artistName = artistName,
|
artistName = artistName,
|
||||||
composer = composer,
|
composer = composer,
|
||||||
albumArtist = albumArtist
|
albumArtist = albumArtist,
|
||||||
|
coverArt = coverArt,
|
||||||
|
genres = genres?.joinToString(";")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +84,9 @@ fun SongEntity.toSong(): Song {
|
||||||
artistId = artistId,
|
artistId = artistId,
|
||||||
artistName = artistName,
|
artistName = artistName,
|
||||||
composer = composer,
|
composer = composer,
|
||||||
albumArtist = albumArtist
|
albumArtist = albumArtist,
|
||||||
|
coverArt = coverArt,
|
||||||
|
genres = genres?.split(';')?.toTypedArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,7 +104,9 @@ fun PlayCountEntity.toSong(): Song {
|
||||||
artistId = artistId,
|
artistId = artistId,
|
||||||
artistName = artistName,
|
artistName = artistName,
|
||||||
composer = composer,
|
composer = composer,
|
||||||
albumArtist = albumArtist
|
albumArtist = albumArtist,
|
||||||
|
coverArt = coverArt,
|
||||||
|
genres = genres?.split(';')?.toTypedArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,7 +124,9 @@ fun HistoryEntity.toSong(): Song {
|
||||||
artistId = artistId,
|
artistId = artistId,
|
||||||
artistName = artistName,
|
artistName = artistName,
|
||||||
composer = composer,
|
composer = composer,
|
||||||
albumArtist = albumArtist
|
albumArtist = albumArtist,
|
||||||
|
coverArt = coverArt,
|
||||||
|
genres = genres?.split(';')?.toTypedArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +146,9 @@ fun Song.toPlayCount(): PlayCountEntity {
|
||||||
composer = composer,
|
composer = composer,
|
||||||
albumArtist = albumArtist,
|
albumArtist = albumArtist,
|
||||||
timePlayed = System.currentTimeMillis(),
|
timePlayed = System.currentTimeMillis(),
|
||||||
playCount = 1
|
playCount = 1,
|
||||||
|
coverArt = coverArt,
|
||||||
|
genres = genres?.joinToString(";")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.util.ArtistSignatureUtil
|
import code.name.monkey.retromusic.util.ArtistSignatureUtil
|
||||||
import code.name.monkey.retromusic.util.CustomArtistImageUtil.Companion.getFile
|
import code.name.monkey.retromusic.util.CustomArtistImageUtil.Companion.getFile
|
||||||
import code.name.monkey.retromusic.util.CustomArtistImageUtil.Companion.getInstance
|
import code.name.monkey.retromusic.util.CustomArtistImageUtil.Companion.getInstance
|
||||||
import code.name.monkey.retromusic.util.MusicUtil.getMediaStoreAlbumCoverUri
|
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
import com.bumptech.glide.GenericTransitionOptions
|
import com.bumptech.glide.GenericTransitionOptions
|
||||||
import com.bumptech.glide.Priority
|
import com.bumptech.glide.Priority
|
||||||
|
|
@ -51,10 +50,11 @@ object RetroGlideExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSongModel(song: Song, ignoreMediaStore: Boolean): Any {
|
private fun getSongModel(song: Song, ignoreMediaStore: Boolean): Any {
|
||||||
return if (ignoreMediaStore) {
|
return if (!song.coverArt.isNullOrEmpty()) {
|
||||||
AudioFileCover(song.data)
|
song.coverArtUri!!
|
||||||
} else {
|
} else {
|
||||||
getMediaStoreAlbumCoverUri(song.albumId)
|
// this won't work but is a fallback for now i guess lol
|
||||||
|
AudioFileCover(song.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,12 @@ import android.os.Bundle
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.repository.RealSongRepository
|
import code.name.monkey.retromusic.repository.RealSongRepository
|
||||||
|
import code.name.monkey.retromusic.repository.SongQuery
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
import java.util.*
|
import software.lavender.music.query.StringFilter
|
||||||
|
|
||||||
object SearchQueryHelper : KoinComponent {
|
object SearchQueryHelper : KoinComponent {
|
||||||
private const val TITLE_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.TITLE + ") = ?"
|
|
||||||
private const val ALBUM_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.ALBUM + ") = ?"
|
|
||||||
private const val ARTIST_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.ARTIST + ") = ?"
|
|
||||||
private const val AND = " AND "
|
|
||||||
private val songRepository by inject<RealSongRepository>()
|
private val songRepository by inject<RealSongRepository>()
|
||||||
var songs = ArrayList<Song>()
|
var songs = ArrayList<Song>()
|
||||||
|
|
||||||
|
|
@ -41,13 +38,10 @@ object SearchQueryHelper : KoinComponent {
|
||||||
var songs = listOf<Song>()
|
var songs = listOf<Song>()
|
||||||
if (artistName != null && albumName != null && titleName != null) {
|
if (artistName != null && albumName != null && titleName != null) {
|
||||||
songs = songRepository.songs(
|
songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(
|
||||||
ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION,
|
artistName = StringFilter(eq = artistName.lowercase(), lower = true),
|
||||||
arrayOf(
|
albumName = StringFilter(eq = albumName.lowercase(), lower = true),
|
||||||
artistName.lowercase(),
|
title = StringFilter(eq = titleName.lowercase(), lower = true)
|
||||||
albumName.lowercase(),
|
|
||||||
titleName.lowercase()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -56,12 +50,9 @@ object SearchQueryHelper : KoinComponent {
|
||||||
}
|
}
|
||||||
if (artistName != null && titleName != null) {
|
if (artistName != null && titleName != null) {
|
||||||
songs = songRepository.songs(
|
songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(
|
||||||
ARTIST_SELECTION + AND + TITLE_SELECTION,
|
artistName = StringFilter(eq = artistName.lowercase(), lower = true),
|
||||||
arrayOf(
|
title = StringFilter(eq = titleName.lowercase(), lower = true)
|
||||||
artistName.lowercase(),
|
|
||||||
titleName.lowercase()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -70,12 +61,9 @@ object SearchQueryHelper : KoinComponent {
|
||||||
}
|
}
|
||||||
if (albumName != null && titleName != null) {
|
if (albumName != null && titleName != null) {
|
||||||
songs = songRepository.songs(
|
songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(
|
||||||
ALBUM_SELECTION + AND + TITLE_SELECTION,
|
albumName = StringFilter(eq = albumName.lowercase(), lower = true),
|
||||||
arrayOf(
|
title = StringFilter(eq = titleName.lowercase(), lower = true)
|
||||||
albumName.lowercase(),
|
|
||||||
titleName.lowercase()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -84,10 +72,7 @@ object SearchQueryHelper : KoinComponent {
|
||||||
}
|
}
|
||||||
if (artistName != null) {
|
if (artistName != null) {
|
||||||
songs = songRepository.songs(
|
songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(artistName = StringFilter(eq = artistName.lowercase(), lower = true))
|
||||||
ARTIST_SELECTION,
|
|
||||||
arrayOf(artistName.lowercase())
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (songs.isNotEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
|
|
@ -95,10 +80,7 @@ object SearchQueryHelper : KoinComponent {
|
||||||
}
|
}
|
||||||
if (albumName != null) {
|
if (albumName != null) {
|
||||||
songs = songRepository.songs(
|
songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(albumName = StringFilter(eq = albumName.lowercase(), lower = true))
|
||||||
ALBUM_SELECTION,
|
|
||||||
arrayOf(albumName.lowercase())
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (songs.isNotEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
|
|
@ -106,42 +88,28 @@ object SearchQueryHelper : KoinComponent {
|
||||||
}
|
}
|
||||||
if (titleName != null) {
|
if (titleName != null) {
|
||||||
songs = songRepository.songs(
|
songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(title = StringFilter(eq = titleName.lowercase(), lower = true))
|
||||||
TITLE_SELECTION,
|
|
||||||
arrayOf(titleName.lowercase())
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (songs.isNotEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
songs = songRepository.songs(
|
songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(artistName = StringFilter(eq = query.lowercase(), lower = true))
|
||||||
ARTIST_SELECTION,
|
|
||||||
arrayOf(query.lowercase())
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (songs.isNotEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
songs = songRepository.songs(
|
songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(albumName = StringFilter(eq = query.lowercase(), lower = true))
|
||||||
ALBUM_SELECTION,
|
|
||||||
arrayOf(query.lowercase())
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if (songs.isNotEmpty()) {
|
if (songs.isNotEmpty()) {
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
songs = songRepository.songs(
|
songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(title = StringFilter(eq = query.lowercase(), lower = true))
|
||||||
TITLE_SELECTION,
|
|
||||||
arrayOf(query.lowercase())
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return if (songs.isNotEmpty()) {
|
return songs.ifEmpty { ArrayList() }
|
||||||
songs
|
|
||||||
} else ArrayList()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ class PlaylistSong(
|
||||||
val playlistId: Long,
|
val playlistId: Long,
|
||||||
val idInPlayList: Long,
|
val idInPlayList: Long,
|
||||||
override val composer: String?,
|
override val composer: String?,
|
||||||
override val albumArtist: String?
|
override val albumArtist: String?,
|
||||||
|
override val coverArt: String?,
|
||||||
|
override val genres: Array<String>?
|
||||||
) : Song(
|
) : Song(
|
||||||
id = id,
|
id = id,
|
||||||
title = title,
|
title = title,
|
||||||
|
|
@ -48,7 +50,9 @@ class PlaylistSong(
|
||||||
artistId = artistId,
|
artistId = artistId,
|
||||||
artistName = artistName,
|
artistName = artistName,
|
||||||
composer = composer,
|
composer = composer,
|
||||||
albumArtist = albumArtist
|
albumArtist = albumArtist,
|
||||||
|
coverArt = coverArt,
|
||||||
|
genres = genres
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
|
@ -73,6 +77,8 @@ class PlaylistSong(
|
||||||
if (idInPlayList != other.idInPlayList) return false
|
if (idInPlayList != other.idInPlayList) return false
|
||||||
if (composer != other.composer) return false
|
if (composer != other.composer) return false
|
||||||
if (albumArtist != other.albumArtist) return false
|
if (albumArtist != other.albumArtist) return false
|
||||||
|
if (coverArt != other.coverArt) return false
|
||||||
|
if (!genres.contentEquals(other.genres)) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +99,9 @@ class PlaylistSong(
|
||||||
result = 31 * result + playlistId.hashCode()
|
result = 31 * result + playlistId.hashCode()
|
||||||
result = 31 * result + idInPlayList.hashCode()
|
result = 31 * result + idInPlayList.hashCode()
|
||||||
result = 31 * result + composer.hashCode()
|
result = 31 * result + composer.hashCode()
|
||||||
result = 31 * result + (albumArtist?.hashCode() ?: 0)
|
result = 31 * result + albumArtist.hashCode()
|
||||||
|
result = 31 * result + coverArt.hashCode()
|
||||||
|
result = 31 * result + genres.contentHashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
*/
|
*/
|
||||||
package code.name.monkey.retromusic.model
|
package code.name.monkey.retromusic.model
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
|
@ -24,6 +25,7 @@ open class Song(
|
||||||
open val trackNumber: Int,
|
open val trackNumber: Int,
|
||||||
open val year: Int,
|
open val year: Int,
|
||||||
open val duration: Long,
|
open val duration: Long,
|
||||||
|
// stream path
|
||||||
open val data: String,
|
open val data: String,
|
||||||
open val dateModified: Long,
|
open val dateModified: Long,
|
||||||
open val albumId: Long,
|
open val albumId: Long,
|
||||||
|
|
@ -31,9 +33,14 @@ open class Song(
|
||||||
open val artistId: Long,
|
open val artistId: Long,
|
||||||
open val artistName: String,
|
open val artistName: String,
|
||||||
open val composer: String?,
|
open val composer: String?,
|
||||||
open val albumArtist: String?
|
open val albumArtist: String?,
|
||||||
|
open val coverArt: String?,
|
||||||
|
// TODO: store genres in a smarter way
|
||||||
|
open val genres: Array<String>?
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
|
val coverArtUri: Uri?
|
||||||
|
get() = Uri.parse(coverArt)
|
||||||
|
|
||||||
// need to override manually because is open and cannot be a data class
|
// need to override manually because is open and cannot be a data class
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
|
@ -55,6 +62,8 @@ open class Song(
|
||||||
if (artistName != other.artistName) return false
|
if (artistName != other.artistName) return false
|
||||||
if (composer != other.composer) return false
|
if (composer != other.composer) return false
|
||||||
if (albumArtist != other.albumArtist) return false
|
if (albumArtist != other.albumArtist) return false
|
||||||
|
if (coverArt != other.coverArt) return false
|
||||||
|
if (!genres.contentEquals(other.genres)) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -73,6 +82,8 @@ open class Song(
|
||||||
result = 31 * result + artistName.hashCode()
|
result = 31 * result + artistName.hashCode()
|
||||||
result = 31 * result + (composer?.hashCode() ?: 0)
|
result = 31 * result + (composer?.hashCode() ?: 0)
|
||||||
result = 31 * result + (albumArtist?.hashCode() ?: 0)
|
result = 31 * result + (albumArtist?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (coverArt?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (genres?.contentHashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,7 +104,9 @@ open class Song(
|
||||||
artistId = -1,
|
artistId = -1,
|
||||||
artistName = "",
|
artistName = "",
|
||||||
composer = "",
|
composer = "",
|
||||||
albumArtist = ""
|
albumArtist = "",
|
||||||
|
coverArt = "",
|
||||||
|
genres = emptyArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -20,16 +20,14 @@ import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
import android.provider.MediaStore.Audio.AudioColumns;
|
import android.provider.MediaStore.Audio.AudioColumns;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import code.name.monkey.retromusic.App;
|
import code.name.monkey.retromusic.App;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
import code.name.monkey.retromusic.repository.RealSongRepository;
|
import code.name.monkey.retromusic.repository.RealSongRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Andrew Neal, modified for Phonograph by Karim Abou Zeid
|
* @author Andrew Neal, modified for Phonograph by Karim Abou Zeid
|
||||||
* <p>This keeps track of the music playback and history state of the playback service
|
* <p>This keeps track of the music playback and history state of the playback service
|
||||||
|
|
@ -157,8 +155,9 @@ public class MusicPlaybackQueueStore extends SQLiteOpenHelper {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private List<Song> getQueue(@NonNull final String tableName) {
|
private List<Song> getQueue(@NonNull final String tableName) {
|
||||||
|
// TODO: add back cursor support after all just for this....
|
||||||
Cursor cursor = getReadableDatabase().query(tableName, null, null, null, null, null, null);
|
Cursor cursor = getReadableDatabase().query(tableName, null, null, null, null, null, null);
|
||||||
return new RealSongRepository(App.Companion.getContext()).songs(cursor);
|
return new RealSongRepository(App.Companion.getContext()).songs();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@
|
||||||
|
|
||||||
package code.name.monkey.retromusic.repository
|
package code.name.monkey.retromusic.repository
|
||||||
|
|
||||||
import android.provider.MediaStore.Audio.AudioColumns
|
|
||||||
import code.name.monkey.retromusic.helper.SortOrder
|
import code.name.monkey.retromusic.helper.SortOrder
|
||||||
import code.name.monkey.retromusic.model.Album
|
import code.name.monkey.retromusic.model.Album
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
|
import software.lavender.music.query.LongFilter
|
||||||
|
import software.lavender.music.query.StringFilter
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -36,34 +37,24 @@ class RealAlbumRepository(private val songRepository: RealSongRepository) :
|
||||||
AlbumRepository {
|
AlbumRepository {
|
||||||
|
|
||||||
override fun albums(): List<Album> {
|
override fun albums(): List<Album> {
|
||||||
|
// TODO: song loader sort order
|
||||||
val songs = songRepository.songs(
|
val songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery()
|
||||||
null,
|
|
||||||
null,
|
|
||||||
getSongLoaderSortOrder()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return splitIntoAlbums(songs)
|
return splitIntoAlbums(songs)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun albums(query: String): List<Album> {
|
override fun albums(query: String): List<Album> {
|
||||||
|
// TODO: song loader sort order
|
||||||
val songs = songRepository.songs(
|
val songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(albumName = StringFilter(like = "%$query%"))
|
||||||
AudioColumns.ALBUM + " LIKE ?",
|
|
||||||
arrayOf("%$query%"),
|
|
||||||
getSongLoaderSortOrder()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return splitIntoAlbums(songs)
|
return splitIntoAlbums(songs)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun album(albumId: Long): Album {
|
override fun album(albumId: Long): Album {
|
||||||
val cursor = songRepository.makeSongCursor(
|
// TODO: song loader sort order
|
||||||
AudioColumns.ALBUM_ID + "=?",
|
val songs = songRepository.songs(SongQuery(albumId = LongFilter(eq = albumId)))
|
||||||
arrayOf(albumId.toString()),
|
|
||||||
getSongLoaderSortOrder()
|
|
||||||
)
|
|
||||||
val songs = songRepository.songs(cursor)
|
|
||||||
val album = Album(albumId, songs)
|
val album = Album(albumId, songs)
|
||||||
sortAlbumSongs(album)
|
sortAlbumSongs(album)
|
||||||
return album
|
return album
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,11 @@
|
||||||
|
|
||||||
package code.name.monkey.retromusic.repository
|
package code.name.monkey.retromusic.repository
|
||||||
|
|
||||||
import android.provider.MediaStore.Audio.AudioColumns
|
|
||||||
import code.name.monkey.retromusic.ALBUM_ARTIST
|
|
||||||
import code.name.monkey.retromusic.helper.SortOrder
|
|
||||||
import code.name.monkey.retromusic.model.Album
|
import code.name.monkey.retromusic.model.Album
|
||||||
import code.name.monkey.retromusic.model.Artist
|
import code.name.monkey.retromusic.model.Artist
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
|
import software.lavender.music.query.LongFilter
|
||||||
|
import software.lavender.music.query.StringFilter
|
||||||
|
|
||||||
interface ArtistRepository {
|
interface ArtistRepository {
|
||||||
fun artists(): List<Artist>
|
fun artists(): List<Artist>
|
||||||
|
|
@ -49,24 +48,19 @@ class RealArtistRepository(
|
||||||
override fun artist(artistId: Long): Artist {
|
override fun artist(artistId: Long): Artist {
|
||||||
if (artistId == Artist.VARIOUS_ARTISTS_ID) {
|
if (artistId == Artist.VARIOUS_ARTISTS_ID) {
|
||||||
// Get Various Artists
|
// Get Various Artists
|
||||||
|
// TODO: song loader sort order
|
||||||
|
// TODO: why is the filtering done AFTER the query here?
|
||||||
val songs = songRepository.songs(
|
val songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery()
|
||||||
null,
|
|
||||||
null,
|
|
||||||
getSongLoaderSortOrder()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
val albums = albumRepository.splitIntoAlbums(songs)
|
val albums = albumRepository.splitIntoAlbums(songs)
|
||||||
.filter { it.albumArtist == Artist.VARIOUS_ARTISTS_DISPLAY_NAME }
|
.filter { it.albumArtist == Artist.VARIOUS_ARTISTS_DISPLAY_NAME }
|
||||||
return Artist(Artist.VARIOUS_ARTISTS_ID, albums)
|
return Artist(Artist.VARIOUS_ARTISTS_ID, albums)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: song loader sort order
|
||||||
val songs = songRepository.songs(
|
val songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(artistId = LongFilter(eq = artistId))
|
||||||
AudioColumns.ARTIST_ID + "=?",
|
|
||||||
arrayOf(artistId.toString()),
|
|
||||||
getSongLoaderSortOrder()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return Artist(artistId, albumRepository.splitIntoAlbums(songs))
|
return Artist(artistId, albumRepository.splitIntoAlbums(songs))
|
||||||
}
|
}
|
||||||
|
|
@ -74,68 +68,51 @@ class RealArtistRepository(
|
||||||
override fun albumArtist(artistName: String): Artist {
|
override fun albumArtist(artistName: String): Artist {
|
||||||
if (artistName == Artist.VARIOUS_ARTISTS_DISPLAY_NAME) {
|
if (artistName == Artist.VARIOUS_ARTISTS_DISPLAY_NAME) {
|
||||||
// Get Various Artists
|
// Get Various Artists
|
||||||
|
// TODO: song loader sort order
|
||||||
|
// TODO: why is the filtering done AFTER the query here?
|
||||||
val songs = songRepository.songs(
|
val songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery()
|
||||||
null,
|
|
||||||
null,
|
|
||||||
getSongLoaderSortOrder()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
val albums = albumRepository.splitIntoAlbums(songs)
|
val albums = albumRepository.splitIntoAlbums(songs)
|
||||||
.filter { it.albumArtist == Artist.VARIOUS_ARTISTS_DISPLAY_NAME }
|
.filter { it.albumArtist == Artist.VARIOUS_ARTISTS_DISPLAY_NAME }
|
||||||
return Artist(Artist.VARIOUS_ARTISTS_ID, albums, true)
|
return Artist(Artist.VARIOUS_ARTISTS_ID, albums, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: song loader sort order
|
||||||
val songs = songRepository.songs(
|
val songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(albumArtist = StringFilter(eq = artistName))
|
||||||
"album_artist" + "=?",
|
|
||||||
arrayOf(artistName),
|
|
||||||
getSongLoaderSortOrder()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return Artist(artistName, albumRepository.splitIntoAlbums(songs), true)
|
return Artist(artistName, albumRepository.splitIntoAlbums(songs), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun artists(): List<Artist> {
|
override fun artists(): List<Artist> {
|
||||||
|
// TODO: song loader sort order
|
||||||
val songs = songRepository.songs(
|
val songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery()
|
||||||
null, null,
|
|
||||||
getSongLoaderSortOrder()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return splitIntoArtists(albumRepository.splitIntoAlbums(songs))
|
return splitIntoArtists(albumRepository.splitIntoAlbums(songs))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun albumArtists(): List<Artist> {
|
override fun albumArtists(): List<Artist> {
|
||||||
|
// TODO: sort "lower($ALBUM_ARTIST)" + if (PreferenceUtil.artistSortOrder == SortOrder.ArtistSortOrder.ARTIST_A_Z) "" else " DESC"
|
||||||
val songs = songRepository.songs(
|
val songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery()
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"lower($ALBUM_ARTIST)" +
|
|
||||||
if (PreferenceUtil.artistSortOrder == SortOrder.ArtistSortOrder.ARTIST_A_Z) "" else " DESC"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return splitIntoAlbumArtists(albumRepository.splitIntoAlbums(songs))
|
return splitIntoAlbumArtists(albumRepository.splitIntoAlbums(songs))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun albumArtists(query: String): List<Artist> {
|
override fun albumArtists(query: String): List<Artist> {
|
||||||
|
// TODO: song loader sort order
|
||||||
val songs = songRepository.songs(
|
val songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(albumArtist = StringFilter(like = "%$query%"))
|
||||||
"album_artist" + " LIKE ?",
|
|
||||||
arrayOf("%$query%"),
|
|
||||||
getSongLoaderSortOrder()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return splitIntoAlbumArtists(albumRepository.splitIntoAlbums(songs))
|
return splitIntoAlbumArtists(albumRepository.splitIntoAlbums(songs))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun artists(query: String): List<Artist> {
|
override fun artists(query: String): List<Artist> {
|
||||||
|
// TODO: song loader sort oder
|
||||||
val songs = songRepository.songs(
|
val songs = songRepository.songs(
|
||||||
songRepository.makeSongCursor(
|
SongQuery(artistName = StringFilter(like = "%$query%"))
|
||||||
AudioColumns.ARTIST + " LIKE ?",
|
|
||||||
arrayOf("%$query%"),
|
|
||||||
getSongLoaderSortOrder()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return splitIntoArtists(albumRepository.splitIntoAlbums(songs))
|
return splitIntoArtists(albumRepository.splitIntoAlbums(songs))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ package code.name.monkey.retromusic.repository
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
|
||||||
import android.provider.BaseColumns
|
import android.provider.BaseColumns
|
||||||
import android.provider.MediaStore.Audio.Genres
|
import android.provider.MediaStore.Audio.Genres
|
||||||
import code.name.monkey.retromusic.Constants.IS_MUSIC
|
import code.name.monkey.retromusic.Constants.IS_MUSIC
|
||||||
|
|
@ -36,6 +35,7 @@ interface GenreRepository {
|
||||||
fun song(genreId: Long): Song
|
fun song(genreId: Long): Song
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move genre info into song model
|
||||||
class RealGenreRepository(
|
class RealGenreRepository(
|
||||||
private val contentResolver: ContentResolver,
|
private val contentResolver: ContentResolver,
|
||||||
private val songRepository: RealSongRepository
|
private val songRepository: RealSongRepository
|
||||||
|
|
@ -50,11 +50,11 @@ class RealGenreRepository(
|
||||||
// so we need to get songs without a genre a different way.
|
// so we need to get songs without a genre a different way.
|
||||||
return if (genreId == -1L) {
|
return if (genreId == -1L) {
|
||||||
getSongsWithNoGenre()
|
getSongsWithNoGenre()
|
||||||
} else songRepository.songs(makeGenreSongCursor(genreId))
|
} else songRepository.songs()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun song(genreId: Long): Song {
|
override fun song(genreId: Long): Song {
|
||||||
return songRepository.song(makeGenreSongCursor(genreId))
|
return songRepository.song(SongQuery())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGenreFromCursor(cursor: Cursor): Genre {
|
private fun getGenreFromCursor(cursor: Cursor): Genre {
|
||||||
|
|
@ -72,31 +72,10 @@ class RealGenreRepository(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSongsWithNoGenre(): List<Song> {
|
private fun getSongsWithNoGenre(): List<Song> {
|
||||||
|
// TODO: do this somehow (probably by actually storing genres on songs in our model)
|
||||||
val selection =
|
val selection =
|
||||||
BaseColumns._ID + " NOT IN " + "(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"
|
BaseColumns._ID + " NOT IN " + "(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"
|
||||||
return songRepository.songs(songRepository.makeSongCursor(selection, null))
|
return songRepository.songs()
|
||||||
}
|
|
||||||
|
|
||||||
private fun hasSongsWithNoGenre(): Boolean {
|
|
||||||
val allSongsCursor = songRepository.makeSongCursor(null, null)
|
|
||||||
val allSongsWithGenreCursor = makeAllSongsWithGenreCursor()
|
|
||||||
|
|
||||||
if (allSongsCursor == null || allSongsWithGenreCursor == null) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasSongsWithNoGenre = allSongsCursor.count > allSongsWithGenreCursor.count
|
|
||||||
allSongsCursor.close()
|
|
||||||
allSongsWithGenreCursor.close()
|
|
||||||
return hasSongsWithNoGenre
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeAllSongsWithGenreCursor(): Cursor? {
|
|
||||||
println(Genres.EXTERNAL_CONTENT_URI.toString())
|
|
||||||
return contentResolver.query(
|
|
||||||
Uri.parse("content://media/external/audio/genres/all/members"),
|
|
||||||
arrayOf(Genres.Members.AUDIO_ID), null, null, null
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeGenreSongCursor(genreId: Long): Cursor? {
|
private fun makeGenreSongCursor(genreId: Long): Cursor? {
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,11 @@
|
||||||
|
|
||||||
package code.name.monkey.retromusic.repository
|
package code.name.monkey.retromusic.repository
|
||||||
|
|
||||||
import android.database.Cursor
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import code.name.monkey.retromusic.model.Album
|
import code.name.monkey.retromusic.model.Album
|
||||||
import code.name.monkey.retromusic.model.Artist
|
import code.name.monkey.retromusic.model.Artist
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||||
|
import software.lavender.music.query.LongFilter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hemanths on 16/08/17.
|
* Created by hemanths on 16/08/17.
|
||||||
|
|
@ -38,7 +37,8 @@ class RealLastAddedRepository(
|
||||||
private val artistRepository: RealArtistRepository
|
private val artistRepository: RealArtistRepository
|
||||||
) : LastAddedRepository {
|
) : LastAddedRepository {
|
||||||
override fun recentSongs(): List<Song> {
|
override fun recentSongs(): List<Song> {
|
||||||
return songRepository.songs(makeLastAddedCursor())
|
// TODO: sort by date added, descending
|
||||||
|
return songRepository.songs(SongQuery(dateModified = LongFilter(gt = PreferenceUtil.lastAddedCutoff)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun recentAlbums(): List<Album> {
|
override fun recentAlbums(): List<Album> {
|
||||||
|
|
@ -48,13 +48,4 @@ class RealLastAddedRepository(
|
||||||
override fun recentArtists(): List<Artist> {
|
override fun recentArtists(): List<Artist> {
|
||||||
return artistRepository.splitIntoArtists(recentAlbums())
|
return artistRepository.splitIntoArtists(recentAlbums())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeLastAddedCursor(): Cursor? {
|
|
||||||
val cutoff = PreferenceUtil.lastAddedCutoff
|
|
||||||
return songRepository.makeSongCursor(
|
|
||||||
MediaStore.Audio.Media.DATE_ADDED + ">?",
|
|
||||||
arrayOf(cutoff.toString()),
|
|
||||||
MediaStore.Audio.Media.DATE_ADDED + " DESC"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ interface PlaylistRepository {
|
||||||
fun playlistSongs(playlistId: Long): List<Song>
|
fun playlistSongs(playlistId: Long): List<Song>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: uncursorify this
|
||||||
class RealPlaylistRepository(
|
class RealPlaylistRepository(
|
||||||
private val contentResolver: ContentResolver
|
private val contentResolver: ContentResolver
|
||||||
) : PlaylistRepository {
|
) : PlaylistRepository {
|
||||||
|
|
@ -168,7 +169,9 @@ class RealPlaylistRepository(
|
||||||
playlistId,
|
playlistId,
|
||||||
idInPlaylist,
|
idInPlaylist,
|
||||||
composer ?: "",
|
composer ?: "",
|
||||||
albumArtist
|
albumArtist,
|
||||||
|
null,
|
||||||
|
emptyArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import code.name.monkey.retromusic.model.Song
|
||||||
* Created by hemanths on 16/08/17.
|
* Created by hemanths on 16/08/17.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO: uncursorify this
|
||||||
object PlaylistSongsLoader {
|
object PlaylistSongsLoader {
|
||||||
|
|
||||||
fun getPlaylistSongList(
|
fun getPlaylistSongList(
|
||||||
|
|
@ -99,7 +100,9 @@ object PlaylistSongsLoader {
|
||||||
playlistId,
|
playlistId,
|
||||||
idInPlaylist,
|
idInPlaylist,
|
||||||
composer,
|
composer,
|
||||||
albumArtist
|
albumArtist,
|
||||||
|
null,
|
||||||
|
emptyArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,21 +15,48 @@
|
||||||
package code.name.monkey.retromusic.repository
|
package code.name.monkey.retromusic.repository
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.provider.MediaStore.Audio.AudioColumns
|
|
||||||
import android.provider.MediaStore.Audio.Media
|
|
||||||
import code.name.monkey.retromusic.Constants.IS_MUSIC
|
|
||||||
import code.name.monkey.retromusic.Constants.baseProjection
|
|
||||||
import code.name.monkey.retromusic.extensions.getInt
|
|
||||||
import code.name.monkey.retromusic.extensions.getLong
|
|
||||||
import code.name.monkey.retromusic.extensions.getString
|
|
||||||
import code.name.monkey.retromusic.extensions.getStringOrNull
|
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.providers.BlacklistStore
|
import software.lavender.music.query.IntFilter
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
import software.lavender.music.query.LongFilter
|
||||||
import java.util.*
|
import software.lavender.music.query.Query
|
||||||
|
import software.lavender.music.query.StringFilter
|
||||||
|
|
||||||
|
// TODO: sorting
|
||||||
|
data class SongQuery(
|
||||||
|
val id: LongFilter? = null,
|
||||||
|
val title: StringFilter? = null,
|
||||||
|
val year: IntFilter? = null,
|
||||||
|
val data: StringFilter? = null,
|
||||||
|
val dateModified: LongFilter? = null,
|
||||||
|
val albumId: LongFilter? = null,
|
||||||
|
val albumName: StringFilter? = null,
|
||||||
|
val artistId: LongFilter? = null,
|
||||||
|
val artistName: StringFilter? = null,
|
||||||
|
val composer: StringFilter? = null,
|
||||||
|
val albumArtist: StringFilter? = null
|
||||||
|
) : Query<Song> {
|
||||||
|
override fun apply(testee: Song): Boolean {
|
||||||
|
if (id != null && !id.matches(testee.id)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (title != null && !title.matches(testee.title)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (year != null && !year.matches(testee.year)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (data != null && !data.matches(testee.data)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (dateModified != null && !dateModified.matches(testee.dateModified)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (albumId != null && !albumId.matches(testee.albumId)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hemanths on 10/08/17.
|
* Created by hemanths on 10/08/17.
|
||||||
|
|
@ -38,13 +65,13 @@ interface SongRepository {
|
||||||
|
|
||||||
fun songs(): List<Song>
|
fun songs(): List<Song>
|
||||||
|
|
||||||
fun songs(cursor: Cursor?): List<Song>
|
fun songs(query: SongQuery): List<Song>
|
||||||
|
|
||||||
fun songs(query: String): List<Song>
|
fun songs(query: String): List<Song>
|
||||||
|
|
||||||
fun songsByFilePath(filePath: String): List<Song>
|
fun songsByFilePath(filePath: String): List<Song>
|
||||||
|
|
||||||
fun song(cursor: Cursor?): Song
|
fun song(query: SongQuery): Song
|
||||||
|
|
||||||
fun song(songId: Long): Song
|
fun song(songId: Long): Song
|
||||||
}
|
}
|
||||||
|
|
@ -52,162 +79,73 @@ interface SongRepository {
|
||||||
class RealSongRepository(private val context: Context) : SongRepository {
|
class RealSongRepository(private val context: Context) : SongRepository {
|
||||||
|
|
||||||
override fun songs(): List<Song> {
|
override fun songs(): List<Song> {
|
||||||
return songs(makeSongCursor(null, null))
|
return listOf(
|
||||||
|
Song(
|
||||||
|
1,
|
||||||
|
"river-lake convergence",
|
||||||
|
1,
|
||||||
|
2021,
|
||||||
|
237234,
|
||||||
|
"https://versary.town/river-lake-convergence.mp3",
|
||||||
|
1639589623,
|
||||||
|
1,
|
||||||
|
"river-lake convergence",
|
||||||
|
1,
|
||||||
|
"annieversary",
|
||||||
|
null,
|
||||||
|
"annieversary",
|
||||||
|
"https://i1.sndcdn.com/artworks-T3nLbND6w681B2ey-YDI6ew-t500x500.jpg",
|
||||||
|
genres = arrayOf("Lakecore")
|
||||||
|
),
|
||||||
|
Song(
|
||||||
|
2,
|
||||||
|
"river-lake convergence 2",
|
||||||
|
1,
|
||||||
|
2021,
|
||||||
|
237234,
|
||||||
|
"https://versary.town/river-lake-convergence.mp3",
|
||||||
|
1639589623,
|
||||||
|
2,
|
||||||
|
"river-lake convergence 2",
|
||||||
|
1,
|
||||||
|
"annieversary",
|
||||||
|
null,
|
||||||
|
"annieversary",
|
||||||
|
"https://i1.sndcdn.com/artworks-T3nLbND6w681B2ey-YDI6ew-t500x500.jpg",
|
||||||
|
genres = arrayOf("Lakecore")
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun songs(cursor: Cursor?): List<Song> {
|
override fun songs(query: SongQuery): List<Song> {
|
||||||
val songs = arrayListOf<Song>()
|
val songs = arrayListOf<Song>()
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
for (song in songs()) {
|
||||||
do {
|
if (query.apply(song)) {
|
||||||
songs.add(getSongFromCursorImpl(cursor))
|
songs.add(song)
|
||||||
} while (cursor.moveToNext())
|
}
|
||||||
}
|
}
|
||||||
cursor?.close()
|
|
||||||
return songs
|
return songs
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun song(cursor: Cursor?): Song {
|
override fun song(query: SongQuery): Song {
|
||||||
val song: Song = if (cursor != null && cursor.moveToFirst()) {
|
val songs = arrayListOf<Song>()
|
||||||
getSongFromCursorImpl(cursor)
|
for (song in songs()) {
|
||||||
} else {
|
if (query.apply(song)) {
|
||||||
Song.emptySong
|
songs.add(song)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cursor?.close()
|
return songs.firstOrNull() ?: Song.emptySong
|
||||||
return song
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun songs(query: String): List<Song> {
|
override fun songs(query: String): List<Song> {
|
||||||
return songs(makeSongCursor(AudioColumns.TITLE + " LIKE ?", arrayOf("%$query%")))
|
return songs(SongQuery(title = StringFilter(like = "%$query%")))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun song(songId: Long): Song {
|
override fun song(songId: Long): Song {
|
||||||
return song(makeSongCursor(AudioColumns._ID + "=?", arrayOf(songId.toString())))
|
return song(SongQuery(id = LongFilter(eq = songId)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun songsByFilePath(filePath: String): List<Song> {
|
override fun songsByFilePath(filePath: String): List<Song> {
|
||||||
return songs(
|
return songs(SongQuery(data = StringFilter(eq = filePath)))
|
||||||
makeSongCursor(
|
|
||||||
AudioColumns.DATA + "=?",
|
|
||||||
arrayOf(filePath)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getSongFromCursorImpl(
|
|
||||||
cursor: Cursor
|
|
||||||
): Song {
|
|
||||||
val id = cursor.getLong(AudioColumns._ID)
|
|
||||||
val title = cursor.getString(AudioColumns.TITLE)
|
|
||||||
val trackNumber = cursor.getInt(AudioColumns.TRACK)
|
|
||||||
val year = cursor.getInt(AudioColumns.YEAR)
|
|
||||||
val duration = cursor.getLong(AudioColumns.DURATION)
|
|
||||||
val data = cursor.getString(AudioColumns.DATA)
|
|
||||||
val dateModified = cursor.getLong(AudioColumns.DATE_MODIFIED)
|
|
||||||
val albumId = cursor.getLong(AudioColumns.ALBUM_ID)
|
|
||||||
val albumName = cursor.getStringOrNull(AudioColumns.ALBUM)
|
|
||||||
val artistId = cursor.getLong(AudioColumns.ARTIST_ID)
|
|
||||||
val artistName = cursor.getStringOrNull(AudioColumns.ARTIST)
|
|
||||||
val composer = cursor.getStringOrNull(AudioColumns.COMPOSER)
|
|
||||||
val albumArtist = cursor.getStringOrNull("album_artist")
|
|
||||||
return Song(
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
trackNumber,
|
|
||||||
year,
|
|
||||||
duration,
|
|
||||||
data,
|
|
||||||
dateModified,
|
|
||||||
albumId,
|
|
||||||
albumName ?: "",
|
|
||||||
artistId,
|
|
||||||
artistName ?: "",
|
|
||||||
composer ?: "",
|
|
||||||
albumArtist ?: ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmOverloads
|
|
||||||
fun makeSongCursor(
|
|
||||||
|
|
||||||
selection: String?,
|
|
||||||
selectionValues: Array<String>?,
|
|
||||||
sortOrder: String = PreferenceUtil.songSortOrder
|
|
||||||
): Cursor? {
|
|
||||||
var selectionFinal = selection
|
|
||||||
var selectionValuesFinal = selectionValues
|
|
||||||
selectionFinal = if (selection != null && selection.trim { it <= ' ' } != "") {
|
|
||||||
"$IS_MUSIC AND $selectionFinal"
|
|
||||||
} else {
|
|
||||||
IS_MUSIC
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whitelist
|
|
||||||
if (PreferenceUtil.isWhiteList) {
|
|
||||||
selectionFinal =
|
|
||||||
selectionFinal + " AND " + AudioColumns.DATA + " LIKE ?"
|
|
||||||
selectionValuesFinal = addSelectionValues(
|
|
||||||
selectionValuesFinal, arrayListOf(
|
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).canonicalPath
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Blacklist
|
|
||||||
val paths = BlacklistStore.getInstance(context).paths
|
|
||||||
if (paths.isNotEmpty()) {
|
|
||||||
selectionFinal = generateBlacklistSelection(selectionFinal, paths.size)
|
|
||||||
selectionValuesFinal = addSelectionValues(selectionValuesFinal, paths)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectionFinal =
|
|
||||||
selectionFinal + " AND " + Media.DURATION + ">= " + (PreferenceUtil.filterLength * 1000)
|
|
||||||
|
|
||||||
val uri = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
|
|
||||||
Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
|
|
||||||
} else {
|
|
||||||
Media.EXTERNAL_CONTENT_URI
|
|
||||||
}
|
|
||||||
return try {
|
|
||||||
context.contentResolver.query(
|
|
||||||
uri,
|
|
||||||
baseProjection,
|
|
||||||
selectionFinal,
|
|
||||||
selectionValuesFinal,
|
|
||||||
sortOrder
|
|
||||||
)
|
|
||||||
} catch (ex: SecurityException) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateBlacklistSelection(
|
|
||||||
selection: String?,
|
|
||||||
pathCount: Int
|
|
||||||
): String {
|
|
||||||
val newSelection = StringBuilder(
|
|
||||||
if (selection != null && selection.trim { it <= ' ' } != "") "$selection AND " else "")
|
|
||||||
newSelection.append(AudioColumns.DATA + " NOT LIKE ?")
|
|
||||||
for (i in 0 until pathCount - 1) {
|
|
||||||
newSelection.append(" AND " + AudioColumns.DATA + " NOT LIKE ?")
|
|
||||||
}
|
|
||||||
return newSelection.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addSelectionValues(
|
|
||||||
selectionValues: Array<String>?,
|
|
||||||
paths: ArrayList<String>
|
|
||||||
): Array<String> {
|
|
||||||
var selectionValuesFinal = selectionValues
|
|
||||||
if (selectionValuesFinal == null) {
|
|
||||||
selectionValuesFinal = emptyArray()
|
|
||||||
}
|
|
||||||
val newSelectionValues = Array(selectionValuesFinal.size + paths.size) {
|
|
||||||
"n = $it"
|
|
||||||
}
|
|
||||||
System.arraycopy(selectionValuesFinal, 0, newSelectionValues, 0, selectionValuesFinal.size)
|
|
||||||
for (i in selectionValuesFinal.size until newSelectionValues.size) {
|
|
||||||
newSelectionValues[i] = paths[i - selectionValuesFinal.size] + "%"
|
|
||||||
}
|
|
||||||
return newSelectionValues
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@ package code.name.monkey.retromusic.repository
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.provider.BaseColumns
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import code.name.monkey.retromusic.Constants.NUMBER_OF_TOP_TRACKS
|
import code.name.monkey.retromusic.Constants.NUMBER_OF_TOP_TRACKS
|
||||||
import code.name.monkey.retromusic.model.Album
|
import code.name.monkey.retromusic.model.Album
|
||||||
import code.name.monkey.retromusic.model.Artist
|
import code.name.monkey.retromusic.model.Artist
|
||||||
|
|
@ -51,29 +49,27 @@ class RealTopPlayedRepository(
|
||||||
) : TopPlayedRepository {
|
) : TopPlayedRepository {
|
||||||
|
|
||||||
override fun recentlyPlayedTracks(): List<Song> {
|
override fun recentlyPlayedTracks(): List<Song> {
|
||||||
return songRepository.songs(makeRecentTracksCursorAndClearUpDatabase())
|
// TODO:
|
||||||
|
return songRepository.songs()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun topTracks(): List<Song> {
|
override fun topTracks(): List<Song> {
|
||||||
return songRepository.songs(makeTopTracksCursorAndClearUpDatabase())
|
// TODO:
|
||||||
|
return songRepository.songs()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notRecentlyPlayedTracks(): List<Song> {
|
override fun notRecentlyPlayedTracks(): List<Song> {
|
||||||
val allSongs = mutableListOf<Song>().apply {
|
val allSongs = mutableListOf<Song>().apply {
|
||||||
addAll(
|
addAll(
|
||||||
songRepository.songs(
|
// TODO: sort by date added asc
|
||||||
songRepository.makeSongCursor(
|
songRepository.songs()
|
||||||
null, null,
|
|
||||||
MediaStore.Audio.Media.DATE_ADDED + " ASC"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val playedSongs = songRepository.songs(
|
val playedSongs = songRepository.songs(
|
||||||
makePlayedTracksCursorAndClearUpDatabase()
|
// TODO: makePlayedTracksCursorAndClearUpDatabase()
|
||||||
)
|
)
|
||||||
val notRecentlyPlayedSongs = songRepository.songs(
|
val notRecentlyPlayedSongs = songRepository.songs(
|
||||||
makeNotRecentTracksCursorAndClearUpDatabase()
|
// TODO: makeNotRecentTracksCursorAndClearUpDatabase()
|
||||||
)
|
)
|
||||||
allSongs.removeAll(playedSongs.toSet())
|
allSongs.removeAll(playedSongs.toSet())
|
||||||
allSongs.addAll(notRecentlyPlayedSongs)
|
allSongs.addAll(notRecentlyPlayedSongs)
|
||||||
|
|
@ -131,7 +127,7 @@ class RealTopPlayedRepository(
|
||||||
cursor: Cursor?, idColumn: Int
|
cursor: Cursor?, idColumn: Int
|
||||||
): SortedLongCursor? {
|
): SortedLongCursor? {
|
||||||
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
/*if (cursor != null && cursor.moveToFirst()) {
|
||||||
// create the list of ids to select against
|
// create the list of ids to select against
|
||||||
val selection = StringBuilder()
|
val selection = StringBuilder()
|
||||||
selection.append(BaseColumns._ID)
|
selection.append(BaseColumns._ID)
|
||||||
|
|
@ -164,7 +160,7 @@ class RealTopPlayedRepository(
|
||||||
BaseColumns._ID
|
BaseColumns._ID
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ class CrossFadePlayer(val context: Context) : Playback, MediaPlayer.OnCompletion
|
||||||
player.setAudioAttributes(
|
player.setAudioAttributes(
|
||||||
AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build()
|
AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build()
|
||||||
)
|
)
|
||||||
player.prepare()
|
player.prepareAsync()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,8 @@ import android.net.Uri;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import code.name.monkey.retromusic.R;
|
import code.name.monkey.retromusic.R;
|
||||||
import code.name.monkey.retromusic.service.playback.Playback;
|
import code.name.monkey.retromusic.service.playback.Playback;
|
||||||
import code.name.monkey.retromusic.util.PreferenceUtil;
|
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||||
|
|
@ -83,7 +81,7 @@ public class MultiPlayer
|
||||||
player.setDataSource(path);
|
player.setDataSource(path);
|
||||||
}
|
}
|
||||||
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||||
player.prepare();
|
player.prepareAsync();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,27 +14,11 @@
|
||||||
|
|
||||||
package code.name.monkey.retromusic.service;
|
package code.name.monkey.retromusic.service;
|
||||||
|
|
||||||
import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE;
|
|
||||||
import static androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT;
|
|
||||||
import static org.koin.java.KoinJavaComponent.get;
|
|
||||||
import static code.name.monkey.retromusic.ConstantsKt.ALBUM_ART_ON_LOCK_SCREEN;
|
|
||||||
import static code.name.monkey.retromusic.ConstantsKt.BLURRED_ALBUM_ART;
|
|
||||||
import static code.name.monkey.retromusic.ConstantsKt.CLASSIC_NOTIFICATION;
|
|
||||||
import static code.name.monkey.retromusic.ConstantsKt.COLORED_NOTIFICATION;
|
|
||||||
import static code.name.monkey.retromusic.ConstantsKt.CROSS_FADE_DURATION;
|
|
||||||
import static code.name.monkey.retromusic.ConstantsKt.TOGGLE_HEADSET;
|
|
||||||
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
|
|
||||||
|
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.appwidget.AppWidgetManager;
|
import android.appwidget.AppWidgetManager;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.*;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.pm.ServiceInfo;
|
import android.content.pm.ServiceInfo;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
|
@ -42,16 +26,10 @@ import android.graphics.Point;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.audiofx.AudioEffect;
|
import android.media.audiofx.AudioEffect;
|
||||||
import android.os.Binder;
|
import android.os.*;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Build.VERSION;
|
import android.os.Build.VERSION;
|
||||||
import android.os.Build.VERSION_CODES;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.HandlerThread;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.support.v4.media.MediaBrowserCompat;
|
import android.support.v4.media.MediaBrowserCompat;
|
||||||
import android.support.v4.media.MediaDescriptionCompat;
|
import android.support.v4.media.MediaDescriptionCompat;
|
||||||
|
|
@ -62,30 +40,14 @@ import android.telephony.PhoneStateListener;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media.MediaBrowserServiceCompat;
|
import androidx.media.MediaBrowserServiceCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.bumptech.glide.RequestBuilder;
|
|
||||||
import com.bumptech.glide.request.target.SimpleTarget;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import code.name.monkey.appthemehelper.util.VersionUtils;
|
import code.name.monkey.appthemehelper.util.VersionUtils;
|
||||||
import code.name.monkey.retromusic.R;
|
import code.name.monkey.retromusic.R;
|
||||||
import code.name.monkey.retromusic.activities.LockScreenActivity;
|
import code.name.monkey.retromusic.activities.LockScreenActivity;
|
||||||
import code.name.monkey.retromusic.appwidgets.AppWidgetBig;
|
import code.name.monkey.retromusic.appwidgets.*;
|
||||||
import code.name.monkey.retromusic.appwidgets.AppWidgetCard;
|
|
||||||
import code.name.monkey.retromusic.appwidgets.AppWidgetClassic;
|
|
||||||
import code.name.monkey.retromusic.appwidgets.AppWidgetMD3;
|
|
||||||
import code.name.monkey.retromusic.appwidgets.AppWidgetSmall;
|
|
||||||
import code.name.monkey.retromusic.appwidgets.AppWidgetText;
|
|
||||||
import code.name.monkey.retromusic.auto.AutoMediaIDHelper;
|
import code.name.monkey.retromusic.auto.AutoMediaIDHelper;
|
||||||
import code.name.monkey.retromusic.auto.AutoMusicProvider;
|
import code.name.monkey.retromusic.auto.AutoMusicProvider;
|
||||||
import code.name.monkey.retromusic.glide.BlurTransformation;
|
import code.name.monkey.retromusic.glide.BlurTransformation;
|
||||||
|
|
@ -108,7 +70,18 @@ import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||||
import code.name.monkey.retromusic.util.RetroUtil;
|
import code.name.monkey.retromusic.util.RetroUtil;
|
||||||
import code.name.monkey.retromusic.volume.AudioVolumeObserver;
|
import code.name.monkey.retromusic.volume.AudioVolumeObserver;
|
||||||
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener;
|
import code.name.monkey.retromusic.volume.OnAudioVolumeChangedListener;
|
||||||
|
import com.bumptech.glide.RequestBuilder;
|
||||||
|
import com.bumptech.glide.request.target.SimpleTarget;
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
|
import software.lavender.music.player.ExoPlayerPlayback;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static android.support.v4.media.MediaBrowserCompat.MediaItem.FLAG_PLAYABLE;
|
||||||
|
import static androidx.media.MediaBrowserServiceCompat.BrowserRoot.EXTRA_RECENT;
|
||||||
|
import static code.name.monkey.retromusic.ConstantsKt.*;
|
||||||
|
import static code.name.monkey.retromusic.service.AudioFader.startFadeAnimator;
|
||||||
|
import static org.koin.java.KoinJavaComponent.get;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Karim Abou Zeid (kabouzeid), Andrew Neal
|
* @author Karim Abou Zeid (kabouzeid), Andrew Neal
|
||||||
|
|
@ -162,6 +135,7 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
public static final int DUCK = 7;
|
public static final int DUCK = 7;
|
||||||
public static final int UNDUCK = 8;
|
public static final int UNDUCK = 8;
|
||||||
public static final int RESTORE_QUEUES = 9;
|
public static final int RESTORE_QUEUES = 9;
|
||||||
|
|
||||||
public static final int SHUFFLE_MODE_NONE = 0;
|
public static final int SHUFFLE_MODE_NONE = 0;
|
||||||
public static final int SHUFFLE_MODE_SHUFFLE = 1;
|
public static final int SHUFFLE_MODE_SHUFFLE = 1;
|
||||||
public static final int REPEAT_MODE_NONE = 0;
|
public static final int REPEAT_MODE_NONE = 0;
|
||||||
|
|
@ -384,7 +358,7 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getTrackUri(@NonNull Song song) {
|
private static String getTrackUri(@NonNull Song song) {
|
||||||
return MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString();
|
return song.getData();//MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -407,8 +381,9 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper());
|
playerHandler = new PlaybackHandler(this, musicPlayerHandlerThread.getLooper());
|
||||||
|
|
||||||
// Set MultiPlayer when crossfade duration is 0 i.e. off
|
// Set MultiPlayer when crossfade duration is 0 i.e. off
|
||||||
|
// TODO: do crossfading in exoplayer or remove the feature entirely for now, crossfadeplayer will NOT work right now
|
||||||
if (PreferenceUtil.INSTANCE.getCrossFadeDuration() == 0) {
|
if (PreferenceUtil.INSTANCE.getCrossFadeDuration() == 0) {
|
||||||
playback = new MultiPlayer(this);
|
playback = new ExoPlayerPlayback(this);
|
||||||
} else {
|
} else {
|
||||||
playback = new CrossFadePlayer(this);
|
playback = new CrossFadePlayer(this);
|
||||||
}
|
}
|
||||||
|
|
@ -879,7 +854,7 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
.setMediaId(String.valueOf(song.getId()))
|
.setMediaId(String.valueOf(song.getId()))
|
||||||
.setTitle(song.getTitle())
|
.setTitle(song.getTitle())
|
||||||
.setSubtitle(song.getArtistName())
|
.setSubtitle(song.getArtistName())
|
||||||
.setIconUri(MusicUtil.getMediaStoreAlbumCoverUri(song.getAlbumId()))
|
.setIconUri(song.getCoverArtUri())
|
||||||
.build(), FLAG_PLAYABLE
|
.build(), FLAG_PLAYABLE
|
||||||
);
|
);
|
||||||
result.sendResult(Collections.singletonList(mediaItem));
|
result.sendResult(Collections.singletonList(mediaItem));
|
||||||
|
|
@ -904,7 +879,7 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
playback.release();
|
playback.release();
|
||||||
}
|
}
|
||||||
playback = null;
|
playback = null;
|
||||||
playback = new MultiPlayer(this);
|
playback = new ExoPlayerPlayback(this);
|
||||||
playback.setCallbacks(this);
|
playback.setCallbacks(this);
|
||||||
if (openTrackAndPrepareNextAt(position)) {
|
if (openTrackAndPrepareNextAt(position)) {
|
||||||
seek(progress);
|
seek(progress);
|
||||||
|
|
@ -1646,6 +1621,7 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent,
|
mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent,
|
||||||
VersionUtils.INSTANCE.hasMarshmallow() ? PendingIntent.FLAG_IMMUTABLE : 0);
|
VersionUtils.INSTANCE.hasMarshmallow() ? PendingIntent.FLAG_IMMUTABLE : 0);
|
||||||
|
|
||||||
|
// TODO: change tag
|
||||||
mediaSession = new MediaSessionCompat(
|
mediaSession = new MediaSessionCompat(
|
||||||
this,
|
this,
|
||||||
"RetroMusicPlayer",
|
"RetroMusicPlayer",
|
||||||
|
|
|
||||||
|
|
@ -15,35 +15,18 @@
|
||||||
package code.name.monkey.retromusic.util;
|
package code.name.monkey.retromusic.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.provider.MediaStore;
|
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileFilter;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
import code.name.monkey.retromusic.adapter.Storage;
|
import code.name.monkey.retromusic.adapter.Storage;
|
||||||
import code.name.monkey.retromusic.model.Song;
|
import code.name.monkey.retromusic.model.Song;
|
||||||
import code.name.monkey.retromusic.repository.RealSongRepository;
|
import code.name.monkey.retromusic.repository.RealSongRepository;
|
||||||
import code.name.monkey.retromusic.repository.SortedCursor;
|
import code.name.monkey.retromusic.repository.SongQuery;
|
||||||
|
import software.lavender.music.query.StringFilter;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
public final class FileUtil {
|
public final class FileUtil {
|
||||||
|
|
||||||
|
|
@ -63,53 +46,31 @@ public final class FileUtil {
|
||||||
@NonNull
|
@NonNull
|
||||||
public static List<Song> matchFilesWithMediaStore(
|
public static List<Song> matchFilesWithMediaStore(
|
||||||
@NonNull Context context, @Nullable List<File> files) {
|
@NonNull Context context, @Nullable List<File> files) {
|
||||||
return new RealSongRepository(context).songs(makeSongCursor(context, files));
|
return new RealSongRepository(context).songs(makeSongQuery(files));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String safeGetCanonicalPath(File file) {
|
public static String safeGetCanonicalPath(File file) {
|
||||||
try {
|
try {
|
||||||
return file.getCanonicalPath();
|
return file.getCanonicalPath();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return file.getAbsolutePath();
|
return file.getAbsolutePath();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static SortedCursor makeSongCursor(
|
|
||||||
@NonNull final Context context, @Nullable final List<File> files) {
|
|
||||||
String selection = null;
|
|
||||||
String[] paths = null;
|
|
||||||
|
|
||||||
if (files != null) {
|
|
||||||
paths = toPathArray(files);
|
|
||||||
|
|
||||||
if (files.size() > 0
|
|
||||||
&& files.size() < 999) { // 999 is the max amount Androids SQL implementation can handle.
|
|
||||||
selection =
|
|
||||||
MediaStore.Audio.AudioColumns.DATA + " IN (" + makePlaceholders(files.size()) + ")";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Cursor songCursor =
|
@Nullable
|
||||||
new RealSongRepository(context).makeSongCursor(selection, selection == null ? null : paths);
|
public static SongQuery makeSongQuery(@Nullable final List<File> files) {
|
||||||
|
String[] paths = null;
|
||||||
|
|
||||||
return songCursor == null
|
if (files != null) {
|
||||||
? null
|
paths = toPathArray(files);
|
||||||
: new SortedCursor(songCursor, paths, MediaStore.Audio.AudioColumns.DATA);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static String makePlaceholders(int len) {
|
return new SongQuery(null, null, null, new StringFilter(null, null, null, paths, false, false), null, null, null, null, null, null, null);
|
||||||
StringBuilder sb = new StringBuilder(len * 2 - 1);
|
|
||||||
sb.append("?");
|
|
||||||
for (int i = 1; i < len; i++) {
|
|
||||||
sb.append(",?");
|
|
||||||
}
|
}
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String[] toPathArray(@Nullable List<File> files) {
|
private static String[] toPathArray(@Nullable List<File> files) {
|
||||||
if (files != null) {
|
if (files != null) {
|
||||||
String[] paths = new String[files.size()];
|
String[] paths = new String[files.size()];
|
||||||
for (int i = 0; i < files.size(); i++) {
|
for (int i = 0; i < files.size(); i++) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package software.lavender.music.extensions
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Looper
|
||||||
|
|
||||||
|
inline val Looper.isCurrentThreadCompat: Boolean
|
||||||
|
get() = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) isCurrentThread else Thread.currentThread() == thread
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
package software.lavender.music.player
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.media.audiofx.AudioEffect
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.HandlerThread
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import code.name.monkey.retromusic.service.playback.Playback
|
||||||
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
|
import com.google.android.exoplayer2.MediaItem
|
||||||
|
import com.google.android.exoplayer2.PlaybackException
|
||||||
|
import com.google.android.exoplayer2.Player
|
||||||
|
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import software.lavender.music.extensions.isCurrentThreadCompat
|
||||||
|
|
||||||
|
// TODO: implement audio offloading, see: https://exoplayer.dev/battery-consumption.html
|
||||||
|
class ExoPlayerPlayback(private val context: Context) : Playback, Player.Listener {
|
||||||
|
private val handler = Handler(HandlerThread("ExoPlayerHandler").apply { start() }.looper)
|
||||||
|
private val dispatcher = handler.asCoroutineDispatcher("ExoPlayerDispatcher")
|
||||||
|
private val player =
|
||||||
|
ExoPlayer.Builder(context).setWakeMode(PowerManager.PARTIAL_WAKE_LOCK).setLooper(handler.looper).build()
|
||||||
|
|
||||||
|
private var initialized: Boolean = false
|
||||||
|
override val isInitialized: Boolean
|
||||||
|
get() = initialized
|
||||||
|
private var playing = false
|
||||||
|
override val isPlaying: Boolean
|
||||||
|
get() = isInitialized && ensurePlayerThread { player.isPlaying }
|
||||||
|
private var sessionId = -1
|
||||||
|
override val audioSessionId: Int
|
||||||
|
get() = ensurePlayerThread { player.audioSessionId }
|
||||||
|
|
||||||
|
private var callbacks: Playback.PlaybackCallbacks? = null
|
||||||
|
private var hasNext = false
|
||||||
|
|
||||||
|
override fun setDataSource(path: String): Boolean {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
path,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
val success = ensurePlayerThread {
|
||||||
|
initialized = false
|
||||||
|
try {
|
||||||
|
player.stop()
|
||||||
|
player.clearMediaItems()
|
||||||
|
player.setMediaItem(MediaItem.fromUri(path))
|
||||||
|
player.prepare()
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
e.message,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
e.printStackTrace()
|
||||||
|
initialized = false
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
return initialized
|
||||||
|
}
|
||||||
|
// TODO: listeners?
|
||||||
|
ensurePlayerThread {
|
||||||
|
player.addListener(this)
|
||||||
|
}
|
||||||
|
val intent = Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)
|
||||||
|
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId)
|
||||||
|
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName)
|
||||||
|
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
|
||||||
|
context.sendBroadcast(intent)
|
||||||
|
initialized = true
|
||||||
|
return initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setNextDataSource(path: String?) = ensurePlayerThread {
|
||||||
|
if (path == null) {
|
||||||
|
return@ensurePlayerThread
|
||||||
|
}
|
||||||
|
player.addMediaItem(MediaItem.fromUri(path))
|
||||||
|
hasNext = true
|
||||||
|
|
||||||
|
// TODO: i think we need to do more stuff here
|
||||||
|
// TODO: gapless?
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCallbacks(callbacks: Playback.PlaybackCallbacks) {
|
||||||
|
this.callbacks = callbacks
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun start() = ensurePlayerThread {
|
||||||
|
try {
|
||||||
|
player.play()
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() = ensurePlayerThread {
|
||||||
|
player.stop()
|
||||||
|
player.clearMediaItems()
|
||||||
|
initialized = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun release() = ensurePlayerThread {
|
||||||
|
player.stop()
|
||||||
|
player.clearMediaItems()
|
||||||
|
initialized = false
|
||||||
|
player.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pause() = ensurePlayerThread {
|
||||||
|
try {
|
||||||
|
player.pause()
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun duration() = if (!initialized) -1 else ensurePlayerThread {
|
||||||
|
try {
|
||||||
|
player.duration.toInt()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun position() = if (!initialized) -1 else ensurePlayerThread {
|
||||||
|
try {
|
||||||
|
player.currentPosition.toInt()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do seeking via
|
||||||
|
override fun seek(whereto: Int) = ensurePlayerThread {
|
||||||
|
try {
|
||||||
|
player.seekTo(whereto.toLong())
|
||||||
|
player.currentPosition.toInt()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setVolume(vol: Float) = ensurePlayerThread {
|
||||||
|
try {
|
||||||
|
player.volume = vol
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAudioSessionId(sessionId: Int) = ensurePlayerThread {
|
||||||
|
try {
|
||||||
|
player.audioSessionId = sessionId
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setCrossFadeDuration(duration: Int) {
|
||||||
|
// TODO: can we do crossfading in exoplayer directly??
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlayerError(error: PlaybackException) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
error.message,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: tbh with first class playlist support in exoplayer we should probably get rid of the weird track switching behaviour asap
|
||||||
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
|
if (hasNext) {
|
||||||
|
hasNext = false
|
||||||
|
callbacks?.onTrackWentToNext()
|
||||||
|
} else {
|
||||||
|
callbacks?.onTrackEnded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
|
playing = isPlaying
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAudioSessionIdChanged(audioSessionId: Int) {
|
||||||
|
sessionId = audioSessionId
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> ensurePlayerThread(block: () -> T): T =
|
||||||
|
if (player.playbackLooper.isCurrentThreadCompat) block() else runBlocking(dispatcher) { block() }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package software.lavender.music.query
|
||||||
|
|
||||||
|
fun String.like(matcher: String): Boolean {
|
||||||
|
if (matcher.startsWith('%')) {
|
||||||
|
if (matcher.endsWith('%')) {
|
||||||
|
return contains(matcher.substring(1, matcher.length - 2))
|
||||||
|
}
|
||||||
|
return endsWith(matcher.substring(1))
|
||||||
|
}
|
||||||
|
if (matcher.endsWith('%')) {
|
||||||
|
return startsWith(matcher.substring(0, matcher.length - 2))
|
||||||
|
}
|
||||||
|
return this == matcher
|
||||||
|
}
|
||||||
87
app/src/main/java/software/lavender/music/query/filters.kt
Normal file
87
app/src/main/java/software/lavender/music/query/filters.kt
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
package software.lavender.music.query
|
||||||
|
|
||||||
|
interface QueryFilter<T> {
|
||||||
|
fun matches(testee: T): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class NumericFilter<T : Number>(
|
||||||
|
val gt: T?,
|
||||||
|
val lt: T?,
|
||||||
|
val eq: T?,
|
||||||
|
val ne: T?
|
||||||
|
) : QueryFilter<T>
|
||||||
|
|
||||||
|
class IntFilter(
|
||||||
|
gt: Int? = null,
|
||||||
|
lt: Int? = null,
|
||||||
|
eq: Int? = null,
|
||||||
|
ne: Int? = null
|
||||||
|
) : NumericFilter<Int>(gt, lt, eq, ne) {
|
||||||
|
override fun matches(testee: Int): Boolean {
|
||||||
|
if (eq != null) {
|
||||||
|
return testee == eq
|
||||||
|
}
|
||||||
|
if (lt != null && testee >= lt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (gt != null && testee <= gt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (ne != null && testee == ne) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LongFilter(
|
||||||
|
gt: Long? = null,
|
||||||
|
lt: Long? = null,
|
||||||
|
eq: Long? = null,
|
||||||
|
ne: Long? = null
|
||||||
|
) : NumericFilter<Long>(gt, lt, eq, ne) {
|
||||||
|
override fun matches(testee: Long): Boolean {
|
||||||
|
if (eq != null) {
|
||||||
|
return testee == eq
|
||||||
|
}
|
||||||
|
if (lt != null && testee >= lt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (gt != null && testee <= gt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (ne != null && testee == ne) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringFilter(
|
||||||
|
val eq: String? = null,
|
||||||
|
val like: String? = null,
|
||||||
|
val ne: String? = null,
|
||||||
|
val _in: Array<String>? = null,
|
||||||
|
val isNull: Boolean = false,
|
||||||
|
val lower: Boolean = false,
|
||||||
|
) : QueryFilter<String?> {
|
||||||
|
override fun matches(testee: String?): Boolean {
|
||||||
|
if (testee == null) {
|
||||||
|
return isNull
|
||||||
|
}
|
||||||
|
val other = if (lower) testee.lowercase() else testee
|
||||||
|
if (eq != null) {
|
||||||
|
return other == eq
|
||||||
|
}
|
||||||
|
if (like != null) {
|
||||||
|
return other.like(like)
|
||||||
|
}
|
||||||
|
if (ne != null) {
|
||||||
|
return other != ne
|
||||||
|
}
|
||||||
|
if (_in != null) {
|
||||||
|
return _in.contains(other)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
5
app/src/main/java/software/lavender/music/query/query.kt
Normal file
5
app/src/main/java/software/lavender/music/query/query.kt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
package software.lavender.music.query
|
||||||
|
|
||||||
|
interface Query<T> {
|
||||||
|
fun apply(testee: T): Boolean
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<item>code_name_monkey_donate_8</item>
|
<item>code_name_monkey_donate_8</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string name="app_name" translatable="false">Retro Music</string>
|
<string name="app_name" translatable="false">Unnamed Player</string>
|
||||||
<string name="git_hub" translatable="false">GitHub</string>
|
<string name="git_hub" translatable="false">GitHub</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue