🙆🏻 Woof that's done it Playlist

Code refactor to Kotlin
main
Hemanth S 2020-08-13 22:38:37 +05:30
parent ff20b3a052
commit e159b1a32a
73 changed files with 1482 additions and 1785 deletions

View File

@ -1,24 +1,70 @@
package code.name.monkey.retromusic
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.albums.AlbumDetailsViewModel
import code.name.monkey.retromusic.fragments.artists.ArtistDetailsViewModel
import code.name.monkey.retromusic.fragments.genres.GenreDetailsViewModel
import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel
import code.name.monkey.retromusic.fragments.search.SearchViewModel
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.network.networkModule
import code.name.monkey.retromusic.providers.RepositoryImpl
import org.eclipse.egit.github.core.Repository
import code.name.monkey.retromusic.repository.*
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.bind
import org.koin.dsl.module
private val dataModule = module {
single {
RepositoryImpl(get(), get())
RealRepository(get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
} bind Repository::class
single {
RealSongRepository(get())
} bind SongRepository::class
single {
RealGenreRepository(get(), get())
} bind GenreRepository::class
single {
RealAlbumRepository(get())
} bind AlbumRepository::class
single {
RealArtistRepository(get(), get())
} bind ArtistRepository::class
single {
RealPlaylistRepository(get())
} bind PlaylistRepository::class
single {
RealTopPlayedRepository(get(), get(), get(), get())
} bind TopPlayedRepository::class
single {
RealLastAddedRepository(
get(),
get(),
get()
)
} bind LastAddedRepository::class
single {
RealSearchRepository(
get(),
get(),
get(),
get(),
get()
)
}
single {
androidContext().contentResolver
}
}
private val viewModules = module {

View File

@ -7,6 +7,7 @@ import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.View
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.activities.base.AbsSlidingMusicPanelActivity
import code.name.monkey.retromusic.extensions.findNavController
@ -16,13 +17,14 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote.openQueue
import code.name.monkey.retromusic.helper.MusicPlayerRemote.playFromUri
import code.name.monkey.retromusic.helper.MusicPlayerRemote.shuffleMode
import code.name.monkey.retromusic.helper.SearchQueryHelper.getSongs
import code.name.monkey.retromusic.loaders.AlbumLoader.getAlbum
import code.name.monkey.retromusic.loaders.ArtistLoader.getArtist
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader.getPlaylistSongList
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.PlaylistSongsLoader.getPlaylistSongList
import code.name.monkey.retromusic.repository.Repository
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.AppRater.appLaunched
import code.name.monkey.retromusic.util.PreferenceUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import java.util.*
@ -33,8 +35,8 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
const val APP_UPDATE_REQUEST_CODE = 9002
}
private val libraryViewModel: LibraryViewModel by inject()
private val repository by inject<Repository>()
private val libraryViewModel by inject<LibraryViewModel>()
private var blockRequestPermissions = false
@ -133,18 +135,22 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
} else if (MediaStore.Audio.Albums.CONTENT_TYPE == mimeType) {
val id = parseIdFromIntent(intent, "albumId", "album").toInt()
if (id >= 0) {
lifecycleScope.launch(Dispatchers.Main) {
val position = intent.getIntExtra("position", 0)
openQueue(getAlbum(this, id).songs!!, position, true)
openQueue(repository.albumById(id).songs!!, position, true)
handled = true
}
}
} else if (MediaStore.Audio.Artists.CONTENT_TYPE == mimeType) {
val id = parseIdFromIntent(intent, "artistId", "artist").toInt()
if (id >= 0) {
lifecycleScope.launch {
val position = intent.getIntExtra("position", 0)
openQueue(getArtist(this, id).songs, position, true)
openQueue(repository.artistById(id).songs, position, true)
handled = true
}
}
}
if (handled) {
setIntent(Intent())
}

View File

@ -14,6 +14,7 @@ import android.view.MenuItem
import android.view.View
import android.view.animation.OvershootInterpolator
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil
@ -23,6 +24,7 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.drawable
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
import code.name.monkey.retromusic.repository.Repository
import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.SAFUtil
import com.google.android.material.button.MaterialButton
@ -31,18 +33,19 @@ import kotlinx.android.synthetic.main.activity_album_tag_editor.*
import org.jaudiotagger.audio.AudioFile
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject
import java.io.File
import java.util.*
abstract class AbsTagEditorActivity : AbsBaseActivity() {
val repository by inject<Repository>()
lateinit var saveFab: MaterialButton
protected var id: Int = 0
private set
private var paletteColorPrimary: Int = 0
private var isInNoImageMode: Boolean = false
private var songPaths: List<String>? = null
lateinit var saveFab: MaterialButton
private var savedSongPaths: List<String>? = null
private val currentSongPath: String? = null
private var savedTags: Map<FieldKey, String>? = null
@ -172,33 +175,27 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(contentViewLayout)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
saveFab = findViewById(R.id.saveTags)
getIntentExtras()
lifecycleScope.launchWhenCreated {
songPaths = getSongPaths()
if (songPaths!!.isEmpty()) {
finish()
return
}
}
setUpViews()
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
}
private fun setUpViews() {
setUpScrollView()
setUpFab()
setUpImageView()
}
private fun setUpScrollView() {
//observableScrollView.setScrollViewCallbacks(observableScrollViewCallbacks);
}
private lateinit var items: List<String>
private fun setUpImageView() {
@ -261,7 +258,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
}
}
protected abstract fun getSongPaths(): List<String>
protected abstract suspend fun getSongPaths(): List<String>
protected fun searchWebFor(vararg keys: String) {
val stringBuilder = StringBuilder()
@ -336,7 +333,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
hideFab()
savedSongPaths = getSongPaths()
savedSongPaths = songPaths
savedTags = fieldKeyValueMap
savedArtworkInfo = artworkInfo

View File

@ -18,7 +18,6 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.loaders.AlbumLoader
import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette
import code.name.monkey.retromusic.util.RetroColorUtil.getColor
@ -31,6 +30,7 @@ import org.jaudiotagger.tag.FieldKey
import java.util.*
class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override val contentViewLayout: Int
get() = R.layout.activity_album_tag_editor
@ -162,13 +162,13 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
writeValuesToFiles(
fieldKeyValueMap,
if (deleteAlbumArt) AbsTagEditorActivity.ArtworkInfo(id, null)
if (deleteAlbumArt) ArtworkInfo(id, null)
else if (albumArtBitmap == null) null else ArtworkInfo(id, albumArtBitmap!!)
)
}
override fun getSongPaths(): List<String> {
val songs = AlbumLoader.getAlbum(this, id).songs
override suspend fun getSongPaths(): List<String> {
val songs = repository.albumById(id).songs
val paths = ArrayList<String>(songs!!.size)
for (song in songs) {
paths.add(song.data)

View File

@ -8,9 +8,10 @@ import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.loaders.SongLoader
import code.name.monkey.retromusic.repository.SongRepository
import kotlinx.android.synthetic.main.activity_song_tag_editor.*
import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject
import java.util.*
class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
@ -18,6 +19,8 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override val contentViewLayout: Int
get() = R.layout.activity_song_tag_editor
private val songRepository by inject<SongRepository>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -85,9 +88,9 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
writeValuesToFiles(fieldKeyValueMap, null)
}
override fun getSongPaths(): List<String> {
override suspend fun getSongPaths(): List<String> {
val paths = ArrayList<String>(1)
paths.add(SongLoader.getSong(this, id).data)
paths.add(songRepository.song(id).data)
return paths
}

View File

@ -63,7 +63,7 @@ public class WriteTagsAsyncTask extends DialogAsyncTask<WriteTagsAsyncTask.Loadi
File albumArtFile = null;
if (info.artworkInfo != null && info.artworkInfo.getArtwork() != null) {
try {
albumArtFile = MusicUtil.createAlbumArtFile().getCanonicalFile();
albumArtFile = MusicUtil.INSTANCE.createAlbumArtFile().getCanonicalFile();
info.artworkInfo.getArtwork()
.compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile));
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile);
@ -120,9 +120,9 @@ public class WriteTagsAsyncTask extends DialogAsyncTask<WriteTagsAsyncTask.Loadi
Context context = getContext();
if (context != null) {
if (wroteArtwork) {
MusicUtil.insertAlbumArt(context, info.artworkInfo.getAlbumId(), albumArtFile.getPath());
MusicUtil.INSTANCE.insertAlbumArt(context, info.artworkInfo.getAlbumId(), albumArtFile.getPath());
} else if (deletedArtwork) {
MusicUtil.deleteAlbumArt(context, info.artworkInfo.getAlbumId());
MusicUtil.INSTANCE.deleteAlbumArt(context, info.artworkInfo.getAlbumId());
}
}

View File

@ -14,9 +14,9 @@ import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.menu.SongMenuHelper
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.util.MusicUtil
import com.bumptech.glide.Glide
@ -93,7 +93,7 @@ class SearchAdapter(
private fun getSongs(playlist: Playlist): java.util.ArrayList<Song> {
val songs = java.util.ArrayList<Song>()
if (playlist is AbsSmartPlaylist) {
songs.addAll(playlist.getSongs(activity))
songs.addAll(playlist.getSongs())
} else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
}

View File

@ -27,6 +27,7 @@ import code.name.monkey.retromusic.adapter.base.AbsMultiSelectAdapter
import code.name.monkey.retromusic.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.audiocover.AudioFileCover
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.interfaces.Callbacks
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide
@ -35,7 +36,6 @@ import com.bumptech.glide.signature.MediaStoreSignature
import me.zhanghai.android.fastscroll.PopupTextProvider
import java.io.File
import java.text.DecimalFormat
import java.util.*
import kotlin.math.log10
import kotlin.math.pow
@ -135,9 +135,9 @@ class SongFileAdapter(
return getFileTitle(`object`)
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<File>) {
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<File>) {
if (callbacks == null) return
callbacks.onMultipleItemAction(menuItem, selection)
callbacks.onMultipleItemAction(menuItem, selection as ArrayList<File>)
}
override fun getPopupText(position: Int): String {
@ -148,13 +148,6 @@ class SongFileAdapter(
return MusicUtil.getSectionName(dataSet[position].name)
}
interface Callbacks {
fun onFileSelected(file: File)
fun onFileMenuClicked(file: File, view: View)
fun onMultipleItemAction(item: MenuItem, files: ArrayList<File>)
}
inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {

View File

@ -129,12 +129,12 @@ open class AlbumAdapter(
}
override fun onMultipleItemAction(
menuItem: MenuItem, selection: ArrayList<Album>
menuItem: MenuItem, selection: List<Album>
) {
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
}
private fun getSongList(albums: List<Album>): ArrayList<Song> {
private fun getSongList(albums: List<Album>): List<Song> {
val songs = ArrayList<Song>()
for (album in albums) {
songs.addAll(album.songs!!)
@ -156,7 +156,6 @@ open class AlbumAdapter(
dataSet[position].year
)
}
return MusicUtil.getSectionName(sectionName)
}

View File

@ -107,12 +107,12 @@ class ArtistAdapter(
}
override fun onMultipleItemAction(
menuItem: MenuItem, selection: ArrayList<Artist>
menuItem: MenuItem, selection: List<Artist>
) {
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId)
}
private fun getSongList(artists: List<Artist>): ArrayList<Song> {
private fun getSongList(artists: List<Artist>): List<Song> {
val songs = ArrayList<Song>()
for (artist in artists) {
songs.addAll(artist.songs) // maybe async in future?

View File

@ -12,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import java.util.ArrayList;
import java.util.List;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.interfaces.CabHolder;
@ -24,7 +25,7 @@ public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I
private final CabHolder cabHolder;
private final Context context;
private MaterialCab cab;
private ArrayList<I> checked;
private List<I> checked;
private int menuRes;
public AbsMultiSelectAdapter(@NonNull Context context, @Nullable CabHolder cabHolder, @MenuRes int menuRes) {
@ -86,7 +87,7 @@ public abstract class AbsMultiSelectAdapter<V extends RecyclerView.ViewHolder, I
return cab != null && cab.isActive();
}
protected abstract void onMultipleItemAction(MenuItem menuItem, ArrayList<I> selection);
protected abstract void onMultipleItemAction(MenuItem menuItem, List<I> selection);
protected void setMultiSelectMenuRes(@MenuRes int menuRes) {
this.menuRes = menuRes;

View File

@ -25,11 +25,11 @@ import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.helper.menu.PlaylistMenuHelper
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader
import code.name.monkey.retromusic.model.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.util.AutoGeneratedPlaylistBitmap
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroColorUtil
@ -122,7 +122,7 @@ class PlaylistAdapter(
return playlist.name
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<Playlist>) {
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Playlist>) {
when (menuItem.itemId) {
else -> SongsMenuHelper.handleMenuClick(
activity,
@ -132,11 +132,11 @@ class PlaylistAdapter(
}
}
private fun getSongList(playlists: List<Playlist>): ArrayList<Song> {
private fun getSongList(playlists: List<Playlist>): List<Song> {
val songs = ArrayList<Song>()
for (playlist in playlists) {
if (playlist is AbsCustomPlaylist) {
songs.addAll(playlist.getSongs(activity))
songs.addAll(playlist.songs())
} else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
}
@ -144,12 +144,12 @@ class PlaylistAdapter(
return songs
}
private fun getSongs(playlist: Playlist): ArrayList<Song> {
private fun getSongs(playlist: Playlist): List<Song> {
val songs = ArrayList<Song>()
if (playlist is AbsSmartPlaylist) {
songs.addAll(playlist.getSongs(activity))
songs.addAll(playlist.songs())
} else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
songs.addAll(playlist.getSongs())
}
return songs
}

View File

@ -51,7 +51,7 @@ class OrderablePlaylistSongAdapter(
return long
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<Song>) {
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
when (menuItem.itemId) {
R.id.action_remove_from_playlist -> {
RemoveFromPlaylistDialog.create(selection as ArrayList<PlaylistSong>)

View File

@ -133,7 +133,7 @@ open class SongAdapter(
return song.title
}
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<Song>) {
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
SongsMenuHelper.handleMenuClick(activity, selection, menuItem.itemId)
}

View File

@ -22,8 +22,8 @@ import code.name.monkey.retromusic.appshortcuts.shortcuttype.ShuffleAllShortcutT
import code.name.monkey.retromusic.appshortcuts.shortcuttype.TopTracksShortcutType
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist
import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist
import code.name.monkey.retromusic.model.smartplaylist.ShuffleAllPlaylist
import code.name.monkey.retromusic.model.smartplaylist.TopTracksPlaylist
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.service.MusicService.*
@ -49,7 +49,7 @@ class AppShortcutLauncherActivity : Activity() {
}
SHORTCUT_TYPE_TOP_TRACKS -> {
startServiceWithPlaylist(
SHUFFLE_MODE_NONE, MyTopTracksPlaylist(applicationContext)
SHUFFLE_MODE_NONE, TopTracksPlaylist(applicationContext)
)
DynamicShortcutManager.reportShortcutUsed(this, TopTracksShortcutType.id)
}

View File

@ -17,30 +17,37 @@ package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.loaders.PlaylistLoader
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.PlaylistRepository
import code.name.monkey.retromusic.util.PlaylistsUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
class AddToPlaylistDialog : DialogFragment() {
private val playlistRepository by inject<PlaylistRepository>()
override fun onCreateDialog(
savedInstanceState: Bundle?
): Dialog {
val playlists = PlaylistLoader.getAllPlaylists(requireContext())
val materialDialog = materialDialog(R.string.add_playlist_title)
lifecycleScope.launch {
val playlists = playlistRepository.playlists()
val playlistNames = mutableListOf<String>()
playlistNames.add(requireContext().resources.getString(R.string.action_new_playlist))
for (p in playlists) {
playlistNames.add(p.name)
}
return materialDialog(R.string.add_playlist_title)
.setItems(playlistNames.toTypedArray()) { _, which ->
withContext(Dispatchers.Main) {
materialDialog.setItems(playlistNames.toTypedArray()) { _, which ->
val songs = extraNotNull<ArrayList<Song>>(EXTRA_SONG).value
if (which == 0) {
CreatePlaylistDialog.create(songs)
@ -55,6 +62,10 @@ class AddToPlaylistDialog : DialogFragment() {
}
dismiss()
}
}
}
return materialDialog(R.string.add_playlist_title)
.create().colorButtons()
}

View File

@ -98,7 +98,9 @@ class DeleteSongsDialog : DialogFragment() {
}
fun deleteSongs(songs: List<Song>, safUris: List<Uri>?) {
MusicUtil.deleteTracks(requireActivity(), songs, safUris) { this.dismiss() }
MusicUtil.deleteTracks(requireActivity(), songs, safUris, Runnable {
dismiss()
})
}
companion object {

View File

@ -16,7 +16,7 @@ import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.android.synthetic.main.fragment_playlist_detail.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
@ -28,7 +28,7 @@ import org.koin.android.ext.android.inject
class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail),
ArtistClickListener, AlbumClickListener {
private val args by navArgs<DetailListFragmentArgs>()
private val repository by inject<RepositoryImpl>()
private val repository by inject<RealRepository>()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)

View File

@ -7,14 +7,14 @@ import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.fragments.ReloadType.*
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class LibraryViewModel(
private val repository: RepositoryImpl
private val realRepository: RealRepository
) : ViewModel(), MusicServiceEventListener {
private val paletteColor = MutableLiveData<Int>()
@ -49,29 +49,29 @@ class LibraryViewModel(
}
private val loadHome: Deferred<List<Home>>
get() = viewModelScope.async { repository.homeSections() }
get() = viewModelScope.async { realRepository.homeSections() }
private val loadSongs: Deferred<List<Song>>
get() = viewModelScope.async(IO) { repository.allSongs() }
get() = viewModelScope.async(IO) { realRepository.allSongs() }
private val loadAlbums: Deferred<List<Album>>
get() = viewModelScope.async(IO) {
repository.allAlbums()
realRepository.allAlbums()
}
private val loadArtists: Deferred<List<Artist>>
get() = viewModelScope.async(IO) {
repository.allArtists()
realRepository.allArtists()
}
private val loadPlaylists: Deferred<List<Playlist>>
get() = viewModelScope.async(IO) {
repository.allPlaylists()
realRepository.allPlaylists()
}
private val loadGenres: Deferred<List<Genre>>
get() = viewModelScope.async(IO) {
repository.allGenres()
realRepository.allGenres()
}

View File

@ -8,14 +8,14 @@ import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.model.LastFmAlbum
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class AlbumDetailsViewModel(
private val repository: RepositoryImpl,
private val realRepository: RealRepository,
private val albumId: Int
) : ViewModel(), MusicServiceEventListener {
@ -39,12 +39,12 @@ class AlbumDetailsViewModel(
}
fun loadAlbumInfo(album: Album) = viewModelScope.launch(Dispatchers.IO) {
val lastFmAlbum = repository.albumInfo(album.artistName ?: "-", album.title ?: "-")
val lastFmAlbum = realRepository.albumInfo(album.artistName ?: "-", album.title ?: "-")
_lastFmAlbum.postValue(lastFmAlbum)
}
fun loadArtist(artistId: Int) = viewModelScope.launch(Dispatchers.IO) {
val artist = repository.artistById(artistId)
val artist = realRepository.artistById(artistId)
_artist.postValue(artist)
artist.albums?.filter { item -> item.id != albumId }?.let { albums ->
@ -54,7 +54,7 @@ class AlbumDetailsViewModel(
private val loadAlbumAsync: Deferred<Album?>
get() = viewModelScope.async(Dispatchers.IO) {
repository.albumById(albumId)
realRepository.albumById(albumId)
}
override fun onMediaStoreChanged() {

View File

@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.network.model.LastFmArtist
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
@ -14,13 +14,13 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class ArtistDetailsViewModel(
private val repository: RepositoryImpl,
private val realRepository: RealRepository,
private val artistId: Int
) : ViewModel(), MusicServiceEventListener {
private val loadArtistDetailsAsync: Deferred<Artist?>
get() = viewModelScope.async(Dispatchers.IO) {
repository.artistById(artistId)
realRepository.artistById(artistId)
}
private val _artist = MutableLiveData<Artist>()
@ -40,7 +40,7 @@ class ArtistDetailsViewModel(
}
fun loadBiography(name: String, lang: String?, cache: String?) = viewModelScope.launch {
val info = repository.artistInfo(name, lang, cache)
val info = realRepository.artistInfo(name, lang, cache)
_lastFmArtist.postValue(info)
}

View File

@ -60,6 +60,7 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote;
import code.name.monkey.retromusic.helper.menu.SongMenuHelper;
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper;
import code.name.monkey.retromusic.interfaces.CabHolder;
import code.name.monkey.retromusic.interfaces.Callbacks;
import code.name.monkey.retromusic.interfaces.MainActivityFragmentCallbacks;
import code.name.monkey.retromusic.misc.DialogAsyncTask;
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
@ -76,7 +77,9 @@ import me.zhanghai.android.fastscroll.FastScroller;
public class FoldersFragment extends AbsMainActivityFragment implements
MainActivityFragmentCallbacks,
CabHolder, BreadCrumbLayout.SelectionCallback, SongFileAdapter.Callbacks,
CabHolder,
BreadCrumbLayout.SelectionCallback,
Callbacks,
LoaderManager.LoaderCallbacks<List<File>> {
public static final String TAG = FoldersFragment.class.getSimpleName();
@ -619,7 +622,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
}
private static class ListSongsAsyncTask
extends ListingFilesDialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, ArrayList<Song>> {
extends ListingFilesDialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, List<Song>> {
private final Object extra;
private WeakReference<OnSongsListedCallback> callbackWeakReference;
@ -633,7 +636,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
}
@Override
protected ArrayList<Song> doInBackground(LoadingInfo... params) {
protected List<Song> doInBackground(LoadingInfo... params) {
try {
LoadingInfo info = params[0];
List<File> files = FileUtil.listFilesDeep(info.files, info.fileFilter);
@ -659,7 +662,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
}
@Override
protected void onPostExecute(ArrayList<Song> songs) {
protected void onPostExecute(List<Song> songs) {
super.onPostExecute(songs);
OnSongsListedCallback callback = checkCallbackReference();
if (songs != null && callback != null) {
@ -692,7 +695,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
public interface OnSongsListedCallback {
void onSongsListed(@NonNull ArrayList<Song> songs, Object extra);
void onSongsListed(@NonNull List<Song> songs, Object extra);
}
static class LoadingInfo {

View File

@ -7,13 +7,13 @@ import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class GenreDetailsViewModel(
private val repository: RepositoryImpl,
private val realRepository: RealRepository,
private val genre: Genre
) : ViewModel(), MusicServiceEventListener {
@ -31,7 +31,7 @@ class GenreDetailsViewModel(
}
private fun loadGenreSongs(genre: Genre) = viewModelScope.launch {
val songs = repository.getGenre(genre.id)
val songs = realRepository.getGenre(genre.id)
withContext(Main) { _playListSongs.postValue(songs) }
}

View File

@ -20,6 +20,7 @@ import android.util.DisplayMetrics
import android.view.View
import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.retromusic.EXTRA_PLAYLIST
@ -31,21 +32,24 @@ import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.ProfileBannerGlideRequest
import code.name.monkey.retromusic.glide.UserProfileGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.loaders.SongLoader
import code.name.monkey.retromusic.model.smartplaylist.HistoryPlaylist
import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist
import code.name.monkey.retromusic.model.smartplaylist.MyTopTracksPlaylist
import code.name.monkey.retromusic.model.smartplaylist.TopTracksPlaylist
import code.name.monkey.retromusic.repository.Repository
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.abs_playlists.*
import kotlinx.android.synthetic.main.fragment_banner_home.*
import kotlinx.android.synthetic.main.home_content.*
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class HomeFragment :
AbsMainActivityFragment(if (PreferenceUtil.isHomeBanner) R.layout.fragment_banner_home else R.layout.fragment_home) {
private val repository by inject<Repository>()
private val libraryViewModel: LibraryViewModel by sharedViewModel()
private val displayMetrics: DisplayMetrics
@ -78,12 +82,17 @@ class HomeFragment :
topPlayed.setOnClickListener {
findActivityNavController(R.id.fragment_container).navigate(
R.id.playlistDetailsFragment,
bundleOf(EXTRA_PLAYLIST to MyTopTracksPlaylist(requireActivity()))
bundleOf(EXTRA_PLAYLIST to TopTracksPlaylist(requireActivity()))
)
}
actionShuffle.setOnClickListener {
MusicPlayerRemote.openAndShuffleQueue(SongLoader.getAllSongs(requireActivity()), true)
lifecycleScope.launch {
MusicPlayerRemote.openAndShuffleQueue(
repository.allSongs(),
true
)
}
}
history.setOnClickListener {

View File

@ -8,6 +8,7 @@ import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.EXTRA_ARTIST_ID
import code.name.monkey.retromusic.R
@ -21,20 +22,21 @@ import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.loaders.ArtistLoader
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics
import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.repository.ArtistRepository
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.fragment_full.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full),
MusicProgressViewUpdateHelper.Callback {
private val artistRepository by inject<ArtistRepository>()
private lateinit var lyricsLayout: FrameLayout
private lateinit var lyricsLine1: TextView
private lateinit var lyricsLine2: TextView
@ -220,9 +222,8 @@ class FullPlayerFragment : AbsPlayerFragment(R.layout.fragment_full),
}
private fun updateArtistImage() {
CoroutineScope(Dispatchers.IO).launch {
val artist =
ArtistLoader.getArtist(requireContext(), MusicPlayerRemote.currentSong.artistId)
lifecycleScope.launch {
val artist = artistRepository.artist(MusicPlayerRemote.currentSong.artistId)
withContext(Dispatchers.Main) {
ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist)
.generatePalette(requireContext())

View File

@ -6,22 +6,20 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.loaders.PlaylistLoader
import code.name.monkey.retromusic.model.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.PlaylistsUtil
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class PlaylistDetailsViewModel(
private val repository: RepositoryImpl,
private val realRepository: RealRepository,
private var playlist: Playlist
) : ViewModel(), MusicServiceEventListener {
private val _playListSongs = MutableLiveData<List<Song>>()
private val _playlist = MutableLiveData<Playlist>().apply {
postValue(playlist)
}
@ -35,7 +33,7 @@ class PlaylistDetailsViewModel(
}
private fun loadPlaylistSongs(playlist: Playlist) = viewModelScope.launch {
val songs = repository.getPlaylistSongs(playlist)
val songs = realRepository.getPlaylistSongs(playlist)
withContext(Main) { _playListSongs.postValue(songs) }
}
@ -50,10 +48,12 @@ class PlaylistDetailsViewModel(
val playlistName =
PlaylistsUtil.getNameForPlaylist(App.getContext(), playlist.id.toLong())
if (playlistName != playlist.name) {
playlist = PlaylistLoader.getPlaylist(App.getContext(), playlist.id)
viewModelScope.launch {
playlist = realRepository.playlist(playlist.id)
_playlist.postValue(playlist)
}
}
}
loadPlaylistSongs(playlist)
}

View File

@ -4,19 +4,19 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.providers.RepositoryImpl
import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SearchViewModel(private val repository: RepositoryImpl) : ViewModel() {
class SearchViewModel(private val realRepository: RealRepository) : ViewModel() {
private val results = MutableLiveData<MutableList<Any>>()
fun getSearchResult(): LiveData<MutableList<Any>> = results
fun search(query: String?) = viewModelScope.launch(IO) {
val result = repository.search(query)
val result = realRepository.search(query)
withContext(Main) { results.postValue(result) }
}
}

View File

@ -34,7 +34,7 @@ public class AlbumGlideRequest {
if (ignoreMediaStore) {
return requestManager.load(new AudioFileCover(song.getData()));
} else {
return requestManager.loadFromMediaStore(MusicUtil.getMediaStoreAlbumCoverUri(song.getAlbumId()));
return requestManager.loadFromMediaStore(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(song.getAlbumId()));
}
}

View File

@ -52,7 +52,7 @@ public class SongGlideRequest {
if (ignoreMediaStore) {
return requestManager.load(new AudioFileCover(song.getData()));
} else {
return requestManager.loadFromMediaStore(MusicUtil.getMediaStoreAlbumCoverUri(song.getAlbumId()));
return requestManager.loadFromMediaStore(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(song.getAlbumId()));
}
}

View File

@ -30,7 +30,7 @@ object M3UWriter : M3UConstants {
): File? {
if (!dir.exists()) dir.mkdirs()
val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION)
val songs = playlist.getSongs(context)
val songs = playlist.getSongs()
if (songs.size > 0) {
val bw = BufferedWriter(FileWriter(file))
bw.write(M3UConstants.HEADER)

View File

@ -23,22 +23,25 @@ import android.os.Build
import android.os.Environment
import android.os.IBinder
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.widget.Toast
import androidx.core.content.ContextCompat
import code.name.monkey.retromusic.loaders.SongLoader
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.SongRepository
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.PreferenceUtil
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.io.File
import java.util.*
object MusicPlayerRemote {
object MusicPlayerRemote : KoinComponent {
val TAG: String = MusicPlayerRemote::class.java.simpleName
private val mConnectionMap = WeakHashMap<Context, ServiceBinder>()
var musicService: MusicService? = null
private val songRepository by inject<SongRepository>()
@JvmStatic
val isPlaying: Boolean
get() = musicService != null && musicService!!.isPlaying
@ -412,21 +415,14 @@ object MusicPlayerRemote {
songId = uri.lastPathSegment
}
if (songId != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
musicService!!,
MediaStore.Audio.AudioColumns._ID + "=?",
arrayOf(songId)
)
)
songs = songRepository.songs(songId)
}
}
}
if (songs == null) {
var songFile: File? = null
if (uri.authority != null && uri.authority == "com.android.externalstorage.documents") {
songFile =
File(
songFile = File(
Environment.getExternalStorageDirectory(),
uri.path?.split(":".toRegex(), 2)?.get(1)
)
@ -440,13 +436,7 @@ object MusicPlayerRemote {
songFile = File(uri.path)
}
if (songFile != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
musicService!!,
MediaStore.Audio.AudioColumns.DATA + "=?",
arrayOf(songFile.absolutePath)
)
)
songs = songRepository.songsByFilePath(songFile.absolutePath)
}
}
if (songs != null && songs.isNotEmpty()) {

View File

@ -18,29 +18,31 @@ import android.app.SearchManager
import android.content.Context
import android.os.Bundle
import android.provider.MediaStore
import code.name.monkey.retromusic.loaders.SongLoader
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.RealSongRepository
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.*
object SearchQueryHelper {
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>()
var songs = ArrayList<Song>()
@JvmStatic
fun getSongs(context: Context, extras: Bundle): ArrayList<Song> {
fun getSongs(context: Context, extras: Bundle): List<Song> {
val query = extras.getString(SearchManager.QUERY, null)
val artistName = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST, null)
val albumName = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM, null)
val titleName = extras.getString(MediaStore.EXTRA_MEDIA_TITLE, null)
var songs = ArrayList<Song>()
var songs = listOf<Song>()
if (artistName != null && albumName != null && titleName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
songs = songRepository.songs(
songRepository.makeSongCursor(
ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION,
arrayOf(
artistName.toLowerCase(),
@ -54,9 +56,8 @@ object SearchQueryHelper {
return songs
}
if (artistName != null && titleName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
songs = songRepository.songs(
songRepository.makeSongCursor(
ARTIST_SELECTION + AND + TITLE_SELECTION,
arrayOf(artistName.toLowerCase(), titleName.toLowerCase())
)
@ -66,9 +67,8 @@ object SearchQueryHelper {
return songs
}
if (albumName != null && titleName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
songs = songRepository.songs(
songRepository.makeSongCursor(
ALBUM_SELECTION + AND + TITLE_SELECTION,
arrayOf(albumName.toLowerCase(), titleName.toLowerCase())
)
@ -78,9 +78,8 @@ object SearchQueryHelper {
return songs
}
if (artistName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
songs = songRepository.songs(
songRepository.makeSongCursor(
ARTIST_SELECTION,
arrayOf(artistName.toLowerCase())
)
@ -90,9 +89,8 @@ object SearchQueryHelper {
return songs
}
if (albumName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
songs = songRepository.songs(
songRepository.makeSongCursor(
ALBUM_SELECTION,
arrayOf(albumName.toLowerCase())
)
@ -102,9 +100,8 @@ object SearchQueryHelper {
return songs
}
if (titleName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
songs = songRepository.songs(
songRepository.makeSongCursor(
TITLE_SELECTION,
arrayOf(titleName.toLowerCase())
)
@ -113,10 +110,8 @@ object SearchQueryHelper {
if (songs.isNotEmpty()) {
return songs
}
songs =
SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
songs = songRepository.songs(
songRepository.makeSongCursor(
ARTIST_SELECTION,
arrayOf(query.toLowerCase())
)
@ -125,9 +120,8 @@ object SearchQueryHelper {
if (songs.isNotEmpty()) {
return songs
}
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
songs = songRepository.songs(
songRepository.makeSongCursor(
ALBUM_SELECTION,
arrayOf(query.toLowerCase())
)
@ -135,9 +129,8 @@ object SearchQueryHelper {
if (songs.isNotEmpty()) {
return songs
}
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
songs = songRepository.songs(
songRepository.makeSongCursor(
TITLE_SELECTION,
arrayOf(query.toLowerCase())
)

View File

@ -14,42 +14,43 @@
package code.name.monkey.retromusic.helper.menu
import android.app.Activity
import android.view.MenuItem
import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.loaders.GenreLoader
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song
import java.util.*
import code.name.monkey.retromusic.repository.GenreRepository
import org.koin.core.KoinComponent
import org.koin.core.inject
object GenreMenuHelper {
object GenreMenuHelper : KoinComponent {
private val genreRepository by inject<GenreRepository>()
fun handleMenuClick(activity: FragmentActivity, genre: Genre, item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_play -> {
MusicPlayerRemote.openQueue(getGenreSongs(activity, genre), 0, true)
MusicPlayerRemote.openQueue(getGenreSongs(genre), 0, true)
return true
}
R.id.action_play_next -> {
MusicPlayerRemote.playNext(getGenreSongs(activity, genre))
MusicPlayerRemote.playNext(getGenreSongs(genre))
return true
}
R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(getGenreSongs(activity, genre))
AddToPlaylistDialog.create(getGenreSongs(genre))
.show(activity.supportFragmentManager, "ADD_PLAYLIST")
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(getGenreSongs(activity, genre))
MusicPlayerRemote.enqueue(getGenreSongs(genre))
return true
}
}
return false
}
private fun getGenreSongs(activity: Activity, genre: Genre): ArrayList<Song> {
return GenreLoader.getSongs(activity, genre.id)
private fun getGenreSongs(genre: Genre): List<Song> {
return genreRepository.songs(genre.id)
}
}

View File

@ -26,13 +26,11 @@ import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.dialogs.DeletePlaylistDialog
import code.name.monkey.retromusic.dialogs.RenamePlaylistDialog
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader
import code.name.monkey.retromusic.misc.WeakContextAsyncTask
import code.name.monkey.retromusic.model.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PlaylistsUtil
import java.util.*
object PlaylistMenuHelper {
@ -80,11 +78,11 @@ object PlaylistMenuHelper {
private fun getPlaylistSongs(
activity: Activity,
playlist: Playlist
): ArrayList<Song> {
): List<Song> {
return if (playlist is AbsCustomPlaylist) {
playlist.getSongs(activity)
playlist.songs()
} else {
PlaylistSongsLoader.getPlaylistSongList(activity, playlist)
playlist.getSongs()
}
}

View File

@ -15,19 +15,17 @@
package code.name.monkey.retromusic.helper.menu
import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.dialogs.DeleteSongsDialog
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song
import java.util.*
object SongsMenuHelper {
fun handleMenuClick(
activity: FragmentActivity,
songs: ArrayList<Song>,
songs: List<Song>,
menuItemId: Int
): Boolean {
when (menuItemId) {

View File

@ -0,0 +1,13 @@
package code.name.monkey.retromusic.interfaces
import android.view.MenuItem
import android.view.View
import java.io.File
interface Callbacks {
fun onFileSelected(file: File)
fun onFileMenuClicked(file: File, view: View)
fun onMultipleItemAction(item: MenuItem, files: ArrayList<File>)
}

View File

@ -1,140 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders
import android.content.Context
import android.database.Cursor
import android.provider.BaseColumns
import android.provider.MediaStore
import android.provider.MediaStore.Audio.PlaylistsColumns
import code.name.monkey.retromusic.model.Playlist
import java.util.*
/**
* Created by hemanths on 16/08/17.
*/
object PlaylistLoader {
private fun getPlaylist(
cursor: Cursor?
): Playlist {
var playlist = Playlist()
if (cursor != null && cursor.moveToFirst()) {
playlist = getPlaylistFromCursorImpl(cursor)
}
cursor?.close()
return playlist
}
fun searchPlaylist(context: Context, searchString: String): List<Playlist> {
return getAllPlaylists(
makePlaylistCursor(
context, PlaylistsColumns.NAME + "=?", arrayOf(searchString)
)
)
}
fun getPlaylist(
context: Context,
playlistName: String
): Playlist {
return getPlaylist(
makePlaylistCursor(
context,
PlaylistsColumns.NAME + "=?",
arrayOf(playlistName)
)
)
}
fun getAllPlaylists(context: Context): ArrayList<Playlist> {
return getAllPlaylists(makePlaylistCursor(context, null, null))
}
fun getFavoritePlaylist(context: Context): ArrayList<Playlist> {
return getAllPlaylists(
makePlaylistCursor(
context,
PlaylistsColumns.NAME + "=?",
arrayOf(context.getString(code.name.monkey.retromusic.R.string.favorites))
)
)
}
fun getAllPlaylists(cursor: Cursor?): ArrayList<Playlist> {
val playlists = ArrayList<Playlist>()
if (cursor != null && cursor.moveToFirst()) {
do {
playlists.add(getPlaylistFromCursorImpl(cursor))
} while (cursor.moveToNext())
}
cursor?.close()
return playlists
}
fun deletePlaylists(context: Context, playlistId: Long) {
val localUri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI
val localStringBuilder = StringBuilder()
localStringBuilder.append("_id IN (")
localStringBuilder.append(playlistId)
localStringBuilder.append(")")
context.contentResolver.delete(localUri, localStringBuilder.toString(), null)
}
private fun makePlaylistCursor(
context: Context,
selection: String?,
values: Array<String>?
): Cursor? {
try {
return context.contentResolver.query(
MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
arrayOf(
BaseColumns._ID, /* 0 */
PlaylistsColumns.NAME /* 1 */
),
selection,
values,
MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER
)
} catch (e: SecurityException) {
return null
}
}
fun getPlaylist(
context: Context,
playlistId: Int
): Playlist {
return getPlaylist(
makePlaylistCursor(
context,
BaseColumns._ID + "=?",
arrayOf(playlistId.toString())
)
)
}
private fun getPlaylistFromCursorImpl(
cursor: Cursor
): Playlist {
val id = cursor.getInt(0)
val name = cursor.getString(1)
return Playlist(id, name)
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.model;
import android.content.Context;
import android.os.Parcel;
import androidx.annotation.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
/**
* @author Karim Abou Zeid (kabouzeid)
*/
public abstract class AbsCustomPlaylist extends Playlist {
public AbsCustomPlaylist(int id, String name) {
super(id, name);
}
public AbsCustomPlaylist() {
}
public AbsCustomPlaylist(Parcel in) {
super(in);
}
@NonNull
public abstract ArrayList<Song> getSongs(@NotNull Context context);
}

View File

@ -0,0 +1,21 @@
package code.name.monkey.retromusic.model
import code.name.monkey.retromusic.repository.LastAddedRepository
import code.name.monkey.retromusic.repository.SongRepository
import code.name.monkey.retromusic.repository.TopPlayedRepository
import org.koin.core.KoinComponent
import org.koin.core.inject
abstract class AbsCustomPlaylist(
id: Int = -1,
name: String = ""
) : Playlist(id, name), KoinComponent {
abstract fun songs(): List<Song>
protected val songRepository by inject<SongRepository>()
protected val topPlayedRepository by inject<TopPlayedRepository>()
protected val lastAddedRepository by inject<LastAddedRepository>()
}

View File

@ -1,120 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.model;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader;
import code.name.monkey.retromusic.util.MusicUtil;
public class Playlist implements Parcelable {
public static final Creator<Playlist> CREATOR = new Creator<Playlist>() {
public Playlist createFromParcel(Parcel source) {
return new Playlist(source);
}
public Playlist[] newArray(int size) {
return new Playlist[size];
}
};
public final int id;
public final String name;
public Playlist(final int id, final String name) {
this.id = id;
this.name = name;
}
public Playlist() {
this.id = -1;
this.name = "";
}
protected Playlist(Parcel in) {
this.id = in.readInt();
this.name = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Playlist playlist = (Playlist) o;
if (id != playlist.id) {
return false;
}
return name != null ? name.equals(playlist.name) : playlist.name == null;
}
@NonNull
public ArrayList<Song> getSongs(@NonNull Context context) {
// this default implementation covers static playlists
return PlaylistSongsLoader.INSTANCE.getPlaylistSongList(context, id);
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Playlist{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.id);
dest.writeString(this.name);
}
@NonNull
public String getInfoString(@NonNull Context context) {
int songCount = getSongs(context).size();
String songCountString = MusicUtil.getSongCountString(context, songCount);
return MusicUtil.buildInfoString(
songCountString,
""
);
}
}

View File

@ -0,0 +1,30 @@
package code.name.monkey.retromusic.model
import android.content.Context
import android.os.Parcelable
import code.name.monkey.retromusic.repository.RealPlaylistRepository
import code.name.monkey.retromusic.util.MusicUtil
import kotlinx.android.parcel.Parcelize
import org.koin.core.KoinComponent
import org.koin.core.get
@Parcelize
open class Playlist(
val id: Int = -1,
val name: String = ""
) : Parcelable, KoinComponent {
// this default implementation covers static playlists
fun getSongs(): List<Song> {
return RealPlaylistRepository(get()).playlistSongs(id)
}
open fun getInfoString(context: Context): String {
val songCount = getSongs().size
val songCountString = MusicUtil.getSongCountString(context, songCount)
return MusicUtil.buildInfoString(
songCountString,
""
)
}
}

View File

@ -1,83 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.model.smartplaylist;
import android.content.Context;
import android.os.Parcel;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.model.AbsCustomPlaylist;
public abstract class AbsSmartPlaylist extends AbsCustomPlaylist {
@DrawableRes
public final int iconRes;
public AbsSmartPlaylist(final String name, final int iconRes) {
super(-Math.abs(31 * name.hashCode() + (iconRes * name.hashCode() * 31 * 31)), name);
this.iconRes = iconRes;
}
public AbsSmartPlaylist() {
super();
this.iconRes = R.drawable.ic_queue_music;
}
protected AbsSmartPlaylist(Parcel in) {
super(in);
this.iconRes = in.readInt();
}
public abstract void clear(Context context);
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(@Nullable final Object obj) {
if (super.equals(obj)) {
if (getClass() != obj.getClass()) {
return false;
}
final AbsSmartPlaylist other = (AbsSmartPlaylist) obj;
return iconRes == other.iconRes;
}
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + iconRes;
return result;
}
public boolean isClearable() {
return true;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.iconRes);
}
}

View File

@ -0,0 +1,10 @@
package code.name.monkey.retromusic.model.smartplaylist
import androidx.annotation.DrawableRes
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.AbsCustomPlaylist
abstract class AbsSmartPlaylist(
name: String = "",
@DrawableRes val iconRes: Int = R.drawable.ic_queue_music
) : AbsCustomPlaylist(-Math.abs(31 * name.hashCode() + iconRes * name.hashCode() * 31 * 31), name)

View File

@ -1,69 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.model.smartplaylist;
import android.content.Context;
import android.os.Parcel;
import androidx.annotation.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.providers.HistoryStore;
/**
* @author Karim Abou Zeid (kabouzeid)
*/
public class HistoryPlaylist extends AbsSmartPlaylist {
public static final Creator<HistoryPlaylist> CREATOR = new Creator<HistoryPlaylist>() {
public HistoryPlaylist createFromParcel(Parcel source) {
return new HistoryPlaylist(source);
}
public HistoryPlaylist[] newArray(int size) {
return new HistoryPlaylist[size];
}
};
public HistoryPlaylist(@NonNull Context context) {
super(context.getString(R.string.history), R.drawable.ic_history);
}
protected HistoryPlaylist(Parcel in) {
super(in);
}
@Override
public void clear(@NonNull Context context) {
HistoryStore.getInstance(context).clear();
}
@Override
public int describeContents() {
return 0;
}
@NonNull
@Override
public ArrayList<Song> getSongs(@NotNull @NonNull Context context) {
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getRecentlyPlayedTracks(context);
}
}

View File

@ -0,0 +1,15 @@
package code.name.monkey.retromusic.model.smartplaylist
import android.content.Context
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Song
import org.koin.core.KoinComponent
class HistoryPlaylist(
context: Context
) : AbsSmartPlaylist(context.getString(R.string.history), R.drawable.ic_history), KoinComponent {
override fun songs(): List<Song> {
return topPlayedRepository.recentlyPlayedTracks()
}
}

View File

@ -1,70 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.model.smartplaylist;
import android.content.Context;
import android.os.Parcel;
import androidx.annotation.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.loaders.LastAddedSongsLoader;
import code.name.monkey.retromusic.model.Song;
public class LastAddedPlaylist extends AbsSmartPlaylist {
public static final Creator<LastAddedPlaylist> CREATOR = new Creator<LastAddedPlaylist>() {
public LastAddedPlaylist createFromParcel(Parcel source) {
return new LastAddedPlaylist(source);
}
public LastAddedPlaylist[] newArray(int size) {
return new LastAddedPlaylist[size];
}
};
public LastAddedPlaylist(@NonNull Context context) {
super(context.getString(R.string.last_added), R.drawable.ic_library_add);
}
protected LastAddedPlaylist(Parcel in) {
super(in);
}
@Override
public void clear(@NonNull Context context) {
}
@Override
public int describeContents() {
return 0;
}
@NonNull
@Override
public ArrayList<Song> getSongs(@NotNull @NonNull Context context) {
return LastAddedSongsLoader.INSTANCE.getLastAddedSongs(context);
}
@Override
public boolean isClearable() {
return false;
}
}

View File

@ -0,0 +1,12 @@
package code.name.monkey.retromusic.model.smartplaylist
import android.content.Context
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Song
class LastAddedPlaylist(context: Context) :
AbsSmartPlaylist(context.getString(R.string.last_added), R.drawable.ic_library_add) {
override fun songs(): List<Song> {
return lastAddedRepository.recentSongs()
}
}

View File

@ -1,69 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.model.smartplaylist;
import android.content.Context;
import android.os.Parcel;
import androidx.annotation.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.providers.SongPlayCountStore;
/**
* @author Karim Abou Zeid (kabouzeid)
*/
public class MyTopTracksPlaylist extends AbsSmartPlaylist {
public static final Creator<MyTopTracksPlaylist> CREATOR = new Creator<MyTopTracksPlaylist>() {
public MyTopTracksPlaylist createFromParcel(Parcel source) {
return new MyTopTracksPlaylist(source);
}
public MyTopTracksPlaylist[] newArray(int size) {
return new MyTopTracksPlaylist[size];
}
};
public MyTopTracksPlaylist(@NonNull Context context) {
super(context.getString(R.string.my_top_tracks), R.drawable.ic_trending_up);
}
protected MyTopTracksPlaylist(Parcel in) {
super(in);
}
@Override
public void clear(@NonNull Context context) {
SongPlayCountStore.getInstance(context).clear();
}
@Override
public int describeContents() {
return 0;
}
@NonNull
@Override
public ArrayList<Song> getSongs(@NotNull @NonNull Context context) {
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getTopTracks(context);
}
}

View File

@ -0,0 +1,13 @@
package code.name.monkey.retromusic.model.smartplaylist
import android.content.Context
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Song
class NotPlayedPlaylist(
context: Context
) : AbsSmartPlaylist(context.getString(R.string.not_recently_played), R.drawable.ic_watch_later) {
override fun songs(): List<Song> {
return topPlayedRepository.notRecentlyPlayedTracks()
}
}

View File

@ -1,69 +0,0 @@
package code.name.monkey.retromusic.model.smartplaylist;
import android.content.Context;
import android.os.Parcel;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.loaders.TopAndRecentlyPlayedTracksLoader;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.util.MusicUtil;
import code.name.monkey.retromusic.util.PreferenceUtil;
/**
* @author SC (soncaokim)
*/
public class NotRecentlyPlayedPlaylist extends AbsSmartPlaylist {
public static final Creator<NotRecentlyPlayedPlaylist> CREATOR = new Creator<NotRecentlyPlayedPlaylist>() {
public NotRecentlyPlayedPlaylist createFromParcel(Parcel source) {
return new NotRecentlyPlayedPlaylist(source);
}
public NotRecentlyPlayedPlaylist[] newArray(int size) {
return new NotRecentlyPlayedPlaylist[size];
}
};
public NotRecentlyPlayedPlaylist(@NonNull Context context) {
super(context.getString(R.string.not_recently_played), R.drawable.ic_watch_later);
}
protected NotRecentlyPlayedPlaylist(Parcel in) {
super(in);
}
@NonNull
@Override
public String getInfoString(@NonNull Context context) {
String cutoff = PreferenceUtil.INSTANCE.getRecentlyPlayedCutoffText(context);
return MusicUtil.buildInfoString(
cutoff,
super.getInfoString(context)
);
}
@NonNull
@Override
public ArrayList<Song> getSongs(@NonNull Context context) {
return TopAndRecentlyPlayedTracksLoader.INSTANCE.getNotRecentlyPlayedTracks(context);
}
@Override
public void clear(@NonNull Context context) {
}
@Override
public boolean isClearable() {
return false;
}
@Override
public int describeContents() {
return 0;
}
}

View File

@ -1,65 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.model.smartplaylist;
import android.content.Context;
import android.os.Parcel;
import androidx.annotation.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.loaders.SongLoader;
import code.name.monkey.retromusic.model.Song;
public class ShuffleAllPlaylist extends AbsSmartPlaylist {
public static final Creator<ShuffleAllPlaylist> CREATOR = new Creator<ShuffleAllPlaylist>() {
public ShuffleAllPlaylist createFromParcel(Parcel source) {
return new ShuffleAllPlaylist(source);
}
public ShuffleAllPlaylist[] newArray(int size) {
return new ShuffleAllPlaylist[size];
}
};
public ShuffleAllPlaylist(@NonNull Context context) {
super(context.getString(R.string.action_shuffle_all), R.drawable.ic_shuffle);
}
protected ShuffleAllPlaylist(Parcel in) {
super(in);
}
@Override
public void clear(@NonNull Context context) {
// Shuffle all is not a real "Smart Playlist"
}
@Override
public int describeContents() {
return 0;
}
@NonNull
@Override
public ArrayList<Song> getSongs(@NotNull Context context) {
return SongLoader.INSTANCE.getAllSongs(context);
}
}

View File

@ -0,0 +1,13 @@
package code.name.monkey.retromusic.model.smartplaylist
import android.content.Context
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Song
class ShuffleAllPlaylist(
context: Context
) : AbsSmartPlaylist(context.getString(R.string.action_shuffle_all), R.drawable.ic_shuffle) {
override fun songs(): List<Song> {
return songRepository.songs()
}
}

View File

@ -0,0 +1,16 @@
package code.name.monkey.retromusic.model.smartplaylist
import android.content.Context
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Song
class TopTracksPlaylist(
context: Context
) : AbsSmartPlaylist(
context.getString(R.string.my_top_tracks),
R.drawable.ic_trending_up
) {
override fun songs(): List<Song> {
return topPlayedRepository.topTracks()
}
}

View File

@ -24,11 +24,11 @@ import android.provider.MediaStore.Audio.AudioColumns;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import code.name.monkey.retromusic.loaders.SongLoader;
import code.name.monkey.retromusic.App;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.repository.RealSongRepository;
/**
* @author Andrew Neal, modified for Phonograph by Karim Abou Zeid
@ -76,12 +76,12 @@ public class MusicPlaybackQueueStore extends SQLiteOpenHelper {
}
@NonNull
public ArrayList<Song> getSavedOriginalPlayingQueue() {
public List<Song> getSavedOriginalPlayingQueue() {
return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
}
@NonNull
public ArrayList<Song> getSavedPlayingQueue() {
public List<Song> getSavedPlayingQueue() {
return getQueue(PLAYING_QUEUE_TABLE_NAME);
}
@ -157,10 +157,10 @@ public class MusicPlaybackQueueStore extends SQLiteOpenHelper {
}
@NonNull
private ArrayList<Song> getQueue(@NonNull final String tableName) {
private List<Song> getQueue(@NonNull final String tableName) {
Cursor cursor = getReadableDatabase().query(tableName, null,
null, null, null, null, null);
return SongLoader.INSTANCE.getSongs(cursor);
return new RealSongRepository(App.Companion.getContext()).songs(cursor);
}
/**

View File

@ -1,89 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.providers.interfaces
import code.name.monkey.retromusic.Result
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.network.model.LastFmAlbum
import code.name.monkey.retromusic.network.model.LastFmArtist
import kotlinx.coroutines.flow.Flow
/**
* Created by hemanths on 11/08/17.
*/
interface Repository {
suspend fun allAlbums(): List<Album>
suspend fun albumById(albumId: Int): Album
suspend fun allSongs(): List<Song>
suspend fun allArtists(): List<Artist>
suspend fun allPlaylists(): List<Playlist>
suspend fun allGenres(): List<Genre>
suspend fun search(query: String?): MutableList<Any>
suspend fun getPlaylistSongs(playlist: Playlist): ArrayList<Song>
suspend fun getGenre(genreId: Int): ArrayList<Song>
suspend fun artistInfo(name: String, lang: String?, cache: String?): LastFmArtist
suspend fun albumInfo(artist: String, album: String): LastFmAlbum
suspend fun artistById(artistId: Int): Artist
suspend fun recentArtists(): List<Artist>
suspend fun topArtists(): List<Artist>
suspend fun topAlbums(): List<Album>
suspend fun recentAlbums(): List<Album>
suspend fun recentArtistsHome(): Home
suspend fun topArtistsHome(): Home
suspend fun topAlbumsHome(): Home
suspend fun recentAlbumsHome(): Home
suspend fun favoritePlaylistHome(): Home
suspend fun suggestionsHome(): Home
suspend fun genresHome(): Home
suspend fun homeSections(): List<Home>
suspend fun homeSectionsFlow(): Flow<Result<List<Home>>>
fun songsFlow(): Flow<Result<List<Song>>>
fun albumsFlow(): Flow<Result<List<Album>>>
fun artistsFlow(): Flow<Result<List<Artist>>>
fun playlistsFlow(): Flow<Result<List<Playlist>>>
fun genresFlow(): Flow<Result<List<Genre>>>
}

View File

@ -12,9 +12,8 @@
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders
package code.name.monkey.retromusic.repository
import android.content.Context
import android.provider.MediaStore.Audio.AudioColumns
import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.model.Album
@ -27,16 +26,31 @@ import kotlin.collections.ArrayList
/**
* Created by hemanths on 11/08/17.
*/
interface AlbumRepository {
fun albums(): List<Album>
object AlbumLoader {
fun albums(query: String): List<Album>
fun getAlbums(
context: Context,
query: String
): ArrayList<Album> {
val songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
fun album(albumId: Int): Album
}
class RealAlbumRepository(private val songRepository: RealSongRepository) :
AlbumRepository {
override fun albums(): List<Album> {
val songs = songRepository.songs(
songRepository.makeSongCursor(
null,
null,
getSongLoaderSortOrder()
)
)
return splitIntoAlbums(songs)
}
override fun albums(query: String): List<Album> {
val songs = songRepository.songs(
songRepository.makeSongCursor(
AudioColumns.ALBUM + " LIKE ?",
arrayOf("%$query%"),
getSongLoaderSortOrder()
@ -45,41 +59,22 @@ object AlbumLoader {
return splitIntoAlbums(songs)
}
@JvmStatic
fun getAlbum(
context: Context,
albumId: Int
): Album {
val songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
override fun album(albumId: Int): Album {
val songs = songRepository.songs(
songRepository.makeSongCursor(
AudioColumns.ALBUM_ID + "=?",
arrayOf(albumId.toString()),
getSongLoaderSortOrder()
)
)
val album = Album(songs)
sortSongsByTrackNumber(album)
val album = Album(ArrayList(songs))
sortAlbumSongs(album)
return album
}
fun getAllAlbums(
context: Context
): ArrayList<Album> {
val songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
null,
null,
getSongLoaderSortOrder()
)
)
return splitIntoAlbums(songs)
}
fun splitIntoAlbums(
songs: ArrayList<Song>?
): ArrayList<Album> {
songs: List<Song>?
): List<Album> {
val albums = ArrayList<Album>()
if (songs != null) {
for (song in songs) {
@ -87,7 +82,7 @@ object AlbumLoader {
}
}
for (album in albums) {
sortSongsByTrackNumber(album)
sortAlbumSongs(album)
}
return albums
}
@ -106,7 +101,7 @@ object AlbumLoader {
return album
}
private fun sortSongsByTrackNumber(album: Album) {
private fun sortAlbumSongs(album: Album) {
when (PreferenceUtil.albumDetailSongSortOrder) {
SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs?.sortWith(Comparator { o1, o2 ->
o1.trackNumber.compareTo(
@ -135,4 +130,6 @@ object AlbumLoader {
return PreferenceUtil.albumSortOrder + ", " +
PreferenceUtil.albumSongSortOrder
}
}

View File

@ -12,46 +12,66 @@
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders
package code.name.monkey.retromusic.repository
import android.content.Context
import android.provider.MediaStore.Audio.AudioColumns
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.util.PreferenceUtil
object ArtistLoader {
interface ArtistRepository {
fun artists(): List<Artist>
fun artists(query: String): List<Artist>
fun artist(artistId: Int): Artist
}
class RealArtistRepository(
private val songRepository: RealSongRepository,
private val albumRepository: RealAlbumRepository
) : ArtistRepository {
private fun getSongLoaderSortOrder(): String {
return PreferenceUtil.artistSortOrder + ", " +
PreferenceUtil.artistAlbumSortOrder + ", " +
PreferenceUtil.artistSongSortOrder
}
fun getAllArtists(context: Context): ArrayList<Artist> {
val songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
override fun artists(): List<Artist> {
val songs = songRepository.songs(
songRepository.makeSongCursor(
null, null,
getSongLoaderSortOrder()
)
)
return splitIntoArtists(AlbumLoader.splitIntoAlbums(songs))
return splitIntoArtists(albumRepository.splitIntoAlbums(songs))
}
fun getArtists(context: Context, query: String): ArrayList<Artist> {
val songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
override fun artists(query: String): List<Artist> {
val songs = songRepository.songs(
songRepository.makeSongCursor(
AudioColumns.ARTIST + " LIKE ?",
arrayOf("%$query%"),
getSongLoaderSortOrder()
)
)
return splitIntoArtists(AlbumLoader.splitIntoAlbums(songs))
return splitIntoArtists(albumRepository.splitIntoAlbums(songs))
}
fun splitIntoArtists(albums: ArrayList<Album>?): ArrayList<Artist> {
val artists = ArrayList<Artist>()
override fun artist(artistId: Int): Artist {
val songs = songRepository.songs(
songRepository.makeSongCursor(
AudioColumns.ARTIST_ID + "=?",
arrayOf(artistId.toString()),
getSongLoaderSortOrder()
)
)
return Artist(ArrayList(albumRepository.splitIntoAlbums(songs)))
}
fun splitIntoArtists(albums: List<Album>?): List<Artist> {
val artists = mutableListOf<Artist>()
if (albums != null) {
for (album in albums) {
getOrCreateArtist(artists, album.artistId).albums!!.add(album)
@ -60,7 +80,7 @@ object ArtistLoader {
return artists
}
private fun getOrCreateArtist(artists: ArrayList<Artist>, artistId: Int): Artist {
private fun getOrCreateArtist(artists: MutableList<Artist>, artistId: Int): Artist {
for (artist in artists) {
if (artist.albums!!.isNotEmpty() && artist.albums[0].songs!!.isNotEmpty() && artist.albums[0].songs!![0].artistId == artistId) {
return artist
@ -70,17 +90,4 @@ object ArtistLoader {
artists.add(album)
return album
}
@JvmStatic
fun getArtist(context: Context, artistId: Int): Artist {
val songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
AudioColumns.ARTIST_ID + "=?",
arrayOf(artistId.toString()),
getSongLoaderSortOrder()
)
)
return Artist(AlbumLoader.splitIntoAlbums(songs))
}
}

View File

@ -12,12 +12,13 @@
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders
package code.name.monkey.retromusic.repository
import android.content.Context
import android.content.ContentResolver
import android.database.Cursor
import android.net.Uri
import android.provider.BaseColumns
import android.provider.MediaStore
import android.provider.MediaStore.Audio.Genres
import code.name.monkey.retromusic.Constants.IS_MUSIC
import code.name.monkey.retromusic.Constants.baseProjection
@ -25,49 +26,52 @@ import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil
interface GenreRepository {
fun genres(): List<Genre>
object GenreLoader {
fun getAllGenres(context: Context): ArrayList<Genre> {
return getGenresFromCursor(context, makeGenreCursor(context))
fun songs(genreId: Int): List<Song>
}
fun searchGenres(context: Context): ArrayList<Genre> {
return getGenresFromCursorForSearch(context, makeGenreCursor(context))
class RealGenreRepository(
private val contentResolver: ContentResolver,
private val songRepository: RealSongRepository
) : GenreRepository {
override fun genres(): List<Genre> {
return getGenresFromCursor(makeGenreCursor())
}
fun getSongs(context: Context, genreId: Int): ArrayList<Song> {
override fun songs(genreId: Int): List<Song> {
// The genres table only stores songs that have a genre specified,
// so we need to get songs without a genre a different way.
return if (genreId == -1) {
getSongsWithNoGenre(context)
} else SongLoader.getSongs(makeGenreSongCursor(context, genreId))
getSongsWithNoGenre()
} else songRepository.songs(makeGenreSongCursor(genreId))
}
private fun getGenreFromCursor(context: Context, cursor: Cursor): Genre {
private fun getGenreFromCursor(cursor: Cursor): Genre {
val id = cursor.getInt(0)
val name = cursor.getString(1)
val songCount = getSongs(context, id).size
val songCount = songs(id).size
return Genre(id, name, songCount)
}
private fun getGenreFromCursorWithOutSongs(context: Context, cursor: Cursor): Genre {
private fun getGenreFromCursorWithOutSongs(cursor: Cursor): Genre {
val id = cursor.getInt(0)
val name = cursor.getString(1)
return Genre(id, name, -1)
}
private fun getSongsWithNoGenre(context: Context): ArrayList<Song> {
val selection = BaseColumns._ID + " NOT IN " +
"(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"
return SongLoader.getSongs(SongLoader.makeSongCursor(context, selection, null))
private fun getSongsWithNoGenre(): List<Song> {
val selection =
BaseColumns._ID + " NOT IN " + "(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"
return songRepository.songs(songRepository.makeSongCursor(selection, null))
}
private fun hasSongsWithNoGenre(context: Context): Boolean {
val allSongsCursor = SongLoader.makeSongCursor(context, null, null)
val allSongsWithGenreCursor = makeAllSongsWithGenreCursor(context)
private fun hasSongsWithNoGenre(): Boolean {
val allSongsCursor = songRepository.makeSongCursor(null, null)
val allSongsWithGenreCursor = makeAllSongsWithGenreCursor()
if (allSongsCursor == null || allSongsWithGenreCursor == null) {
return false
@ -79,44 +83,36 @@ object GenreLoader {
return hasSongsWithNoGenre
}
private fun makeAllSongsWithGenreCursor(context: Context): Cursor? {
return try {
context.contentResolver.query(
private fun makeAllSongsWithGenreCursor(): Cursor? {
println(MediaStore.Audio.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
)
} catch (e: SecurityException) {
null
}
}
private fun makeGenreSongCursor(context: Context, genreId: Int): Cursor? {
try {
return context.contentResolver.query(
private fun makeGenreSongCursor(genreId: Int): Cursor? {
return contentResolver.query(
Genres.Members.getContentUri("external", genreId.toLong()),
baseProjection,
IS_MUSIC,
null,
PreferenceUtil.songSortOrder
)
} catch (e: SecurityException) {
return null
}
}
private fun getGenresFromCursor(context: Context, cursor: Cursor?): ArrayList<Genre> {
private fun getGenresFromCursor(cursor: Cursor?): ArrayList<Genre> {
val genres = arrayListOf<Genre>()
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
val genre = getGenreFromCursor(context, cursor)
val genre = getGenreFromCursor(cursor)
if (genre.songCount > 0) {
genres.add(genre)
} else {
// try to remove the empty genre from the media store
try {
context.contentResolver.delete(
contentResolver.delete(
Genres.EXTERNAL_CONTENT_URI,
Genres._ID + " == " + genre.id,
null
@ -133,11 +129,11 @@ object GenreLoader {
return genres
}
private fun getGenresFromCursorForSearch(context: Context, cursor: Cursor?): ArrayList<Genre> {
val genres = arrayListOf<Genre>()
private fun getGenresFromCursorForSearch(cursor: Cursor?): List<Genre> {
val genres = mutableListOf<Genre>()
if (cursor != null && cursor.moveToFirst()) {
do {
genres.add(getGenreFromCursorWithOutSongs(context, cursor))
genres.add(getGenreFromCursorWithOutSongs(cursor))
} while (cursor.moveToNext())
}
cursor?.close()
@ -145,18 +141,16 @@ object GenreLoader {
}
private fun makeGenreCursor(context: Context): Cursor? {
private fun makeGenreCursor(): Cursor? {
val projection = arrayOf(Genres._ID, Genres.NAME)
try {
return context.contentResolver.query(
return contentResolver.query(
Genres.EXTERNAL_CONTENT_URI,
projection,
null,
null,
PreferenceUtil.genreSortOrder
)
} catch (e: SecurityException) {
return null
}
}
}

View File

@ -12,9 +12,8 @@
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders
package code.name.monkey.retromusic.repository
import android.content.Context
import android.database.Cursor
import android.provider.MediaStore
import code.name.monkey.retromusic.model.Album
@ -25,28 +24,37 @@ import code.name.monkey.retromusic.util.PreferenceUtil
/**
* Created by hemanths on 16/08/17.
*/
interface LastAddedRepository {
fun recentSongs(): List<Song>
object LastAddedSongsLoader {
fun recentAlbums(): List<Album>
fun getLastAddedSongs(context: Context): ArrayList<Song> {
return SongLoader.getSongs(makeLastAddedCursor(context))
fun recentArtists(): List<Artist>
}
private fun makeLastAddedCursor(context: Context): Cursor? {
class RealLastAddedRepository(
private val songRepository: RealSongRepository,
private val albumRepository: RealAlbumRepository,
private val artistRepository: RealArtistRepository
) : LastAddedRepository {
override fun recentSongs(): List<Song> {
return songRepository.songs(makeLastAddedCursor())
}
override fun recentAlbums(): List<Album> {
return albumRepository.splitIntoAlbums(recentSongs())
}
override fun recentArtists(): List<Artist> {
return artistRepository.splitIntoArtists(recentAlbums())
}
private fun makeLastAddedCursor(): Cursor? {
val cutoff = PreferenceUtil.lastAddedCutoff
return SongLoader.makeSongCursor(
context,
return songRepository.makeSongCursor(
MediaStore.Audio.Media.DATE_ADDED + ">?",
arrayOf(cutoff.toString()),
MediaStore.Audio.Media.DATE_ADDED + " DESC"
)
}
fun getLastAddedAlbums(context: Context): ArrayList<Album> {
return AlbumLoader.splitIntoAlbums(getLastAddedSongs(context))
}
fun getLastAddedArtists(context: Context): ArrayList<Artist> {
return ArtistLoader.splitIntoArtists(getLastAddedAlbums(context))
}
}

View File

@ -0,0 +1,206 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.repository
import android.content.ContentResolver
import android.database.Cursor
import android.provider.BaseColumns
import android.provider.MediaStore
import android.provider.MediaStore.Audio.PlaylistsColumns
import code.name.monkey.retromusic.Constants
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.PlaylistSong
import code.name.monkey.retromusic.model.Song
/**
* Created by hemanths on 16/08/17.
*/
interface PlaylistRepository {
fun playlist(cursor: Cursor?): Playlist
fun searchPlaylist(query: String): List<Playlist>
fun playlist(playlistName: String): Playlist
fun playlists(): List<Playlist>
fun playlists(cursor: Cursor?): List<Playlist>
fun favoritePlaylist(playlistName: String): List<Playlist>
fun deletePlaylist(playlistId: Int)
fun playlist(playlistId: Int): Playlist
fun playlistSongs(playlistId: Int): List<Song>
}
class RealPlaylistRepository(
private val contentResolver: ContentResolver
) : PlaylistRepository {
override fun playlist(cursor: Cursor?): Playlist {
var playlist = Playlist()
if (cursor != null && cursor.moveToFirst()) {
playlist = getPlaylistFromCursorImpl(cursor)
}
cursor?.close()
return playlist
}
override fun playlist(playlistName: String): Playlist {
return playlist(makePlaylistCursor(PlaylistsColumns.NAME + "=?", arrayOf(playlistName)))
}
override fun playlist(playlistId: Int): Playlist {
return playlist(
makePlaylistCursor(
BaseColumns._ID + "=?",
arrayOf(playlistId.toString())
)
)
}
override fun searchPlaylist(query: String): List<Playlist> {
return playlists(makePlaylistCursor(PlaylistsColumns.NAME + "=?", arrayOf(query)))
}
override fun playlists(): List<Playlist> {
return playlists(makePlaylistCursor(null, null))
}
override fun playlists(cursor: Cursor?): List<Playlist> {
val playlists = mutableListOf<Playlist>()
if (cursor != null && cursor.moveToFirst()) {
do {
playlists.add(getPlaylistFromCursorImpl(cursor))
} while (cursor.moveToNext())
}
cursor?.close()
return playlists
}
override fun favoritePlaylist(playlistName: String): List<Playlist> {
return playlists(
makePlaylistCursor(
PlaylistsColumns.NAME + "=?",
arrayOf(playlistName)
)
)
}
override fun deletePlaylist(playlistId: Int) {
val localUri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI
val localStringBuilder = StringBuilder()
localStringBuilder.append("_id IN (")
localStringBuilder.append(playlistId)
localStringBuilder.append(")")
contentResolver.delete(localUri, localStringBuilder.toString(), null)
}
private fun getPlaylistFromCursorImpl(
cursor: Cursor
): Playlist {
val id = cursor.getInt(0)
val name = cursor.getString(1)
return Playlist(id, name)
}
override fun playlistSongs(playlistId: Int): List<Song> {
val songs = arrayListOf<Song>()
val cursor = makePlaylistSongCursor(playlistId)
if (cursor != null && cursor.moveToFirst()) {
do {
songs.add(getPlaylistSongFromCursorImpl(cursor, playlistId))
} while (cursor.moveToNext())
}
cursor?.close()
return songs
}
private fun getPlaylistSongFromCursorImpl(cursor: Cursor, playlistId: Int): PlaylistSong {
val id = cursor.getInt(0)
val title = cursor.getString(1)
val trackNumber = cursor.getInt(2)
val year = cursor.getInt(3)
val duration = cursor.getLong(4)
val data = cursor.getString(5)
val dateModified = cursor.getLong(6)
val albumId = cursor.getInt(7)
val albumName = cursor.getString(8)
val artistId = cursor.getInt(9)
val artistName = cursor.getString(10)
val idInPlaylist = cursor.getInt(11)
val composer = cursor.getString(12)
val albumArtist = cursor.getString(13)
return PlaylistSong(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
playlistId,
idInPlaylist,
composer,
albumArtist
)
}
private fun makePlaylistCursor(
selection: String?,
values: Array<String>?
): Cursor? {
return contentResolver.query(
MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
arrayOf(
BaseColumns._ID, /* 0 */
PlaylistsColumns.NAME /* 1 */
),
selection,
values,
MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER
)
}
private fun makePlaylistSongCursor(playlistId: Int): Cursor? {
return contentResolver.query(
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId.toLong()),
arrayOf(
MediaStore.Audio.Playlists.Members.AUDIO_ID, // 0
MediaStore.Audio.AudioColumns.TITLE, // 1
MediaStore.Audio.AudioColumns.TRACK, // 2
MediaStore.Audio.AudioColumns.YEAR, // 3
MediaStore.Audio.AudioColumns.DURATION, // 4
MediaStore.Audio.AudioColumns.DATA, // 5
MediaStore.Audio.AudioColumns.DATE_MODIFIED, // 6
MediaStore.Audio.AudioColumns.ALBUM_ID, // 7
MediaStore.Audio.AudioColumns.ALBUM, // 8
MediaStore.Audio.AudioColumns.ARTIST_ID, // 9
MediaStore.Audio.AudioColumns.ARTIST, // 10
MediaStore.Audio.Playlists.Members._ID,//11
MediaStore.Audio.AudioColumns.COMPOSER,//12
"album_artist"//13
), Constants.IS_MUSIC, null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER
)
}
}

View File

@ -12,7 +12,7 @@
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders
package code.name.monkey.retromusic.repository
import android.content.Context
import android.database.Cursor
@ -34,19 +34,31 @@ object PlaylistSongsLoader {
fun getPlaylistSongList(
context: Context,
playlist: Playlist
): ArrayList<Song> {
return (playlist as? AbsCustomPlaylist)?.getSongs(context)
?: getPlaylistSongList(context, playlist.id)
): List<Song> {
return if (playlist is AbsCustomPlaylist) {
return playlist.songs()
} else {
getPlaylistSongList(context, playlist.id)
}
}
@JvmStatic
fun getPlaylistSongList(context: Context, playlistId: Int): ArrayList<Song> {
val songs = arrayListOf<Song>()
val cursor = makePlaylistSongCursor(context, playlistId)
val cursor =
makePlaylistSongCursor(
context,
playlistId
)
if (cursor != null && cursor.moveToFirst()) {
do {
songs.add(getPlaylistSongFromCursorImpl(cursor, playlistId))
songs.add(
getPlaylistSongFromCursorImpl(
cursor,
playlistId
)
)
} while (cursor.moveToNext())
}
cursor?.close()

View File

@ -12,68 +12,133 @@
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.providers
package code.name.monkey.retromusic.repository
import android.content.Context
import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.loaders.*
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.smartplaylist.NotRecentlyPlayedPlaylist
import code.name.monkey.retromusic.model.smartplaylist.NotPlayedPlaylist
import code.name.monkey.retromusic.network.LastFMService
import code.name.monkey.retromusic.network.model.LastFmAlbum
import code.name.monkey.retromusic.network.model.LastFmArtist
import code.name.monkey.retromusic.providers.interfaces.Repository
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flow
class RepositoryImpl(
interface Repository {
suspend fun allAlbums(): List<Album>
suspend fun albumById(albumId: Int): Album
suspend fun allSongs(): List<Song>
suspend fun allArtists(): List<Artist>
suspend fun allPlaylists(): List<Playlist>
suspend fun allGenres(): List<Genre>
suspend fun search(query: String?): MutableList<Any>
suspend fun getPlaylistSongs(playlist: Playlist): List<Song>
suspend fun getGenre(genreId: Int): List<Song>
suspend fun artistInfo(name: String, lang: String?, cache: String?): LastFmArtist
suspend fun albumInfo(artist: String, album: String): LastFmAlbum
suspend fun artistById(artistId: Int): Artist
suspend fun recentArtists(): List<Artist>
suspend fun topArtists(): List<Artist>
suspend fun topAlbums(): List<Album>
suspend fun recentAlbums(): List<Album>
suspend fun recentArtistsHome(): Home
suspend fun topArtistsHome(): Home
suspend fun topAlbumsHome(): Home
suspend fun recentAlbumsHome(): Home
suspend fun favoritePlaylistHome(): Home
suspend fun suggestionsHome(): Home
suspend fun genresHome(): Home
suspend fun homeSections(): List<Home>
suspend fun homeSectionsFlow(): Flow<Result<List<Home>>>
fun songsFlow(): Flow<Result<List<Song>>>
fun albumsFlow(): Flow<Result<List<Album>>>
fun artistsFlow(): Flow<Result<List<Artist>>>
fun playlistsFlow(): Flow<Result<List<Playlist>>>
fun genresFlow(): Flow<Result<List<Genre>>>
suspend fun playlist(playlistId: Int): Playlist
}
class RealRepository(
private val context: Context,
private val lastFMService: LastFMService
private val lastFMService: LastFMService,
private val songRepository: SongRepository,
private val albumRepository: AlbumRepository,
private val artistRepository: ArtistRepository,
private val genreRepository: GenreRepository,
private val lastAddedRepository: LastAddedRepository,
private val playlistRepository: PlaylistRepository,
private val searchRepository: RealSearchRepository,
private val playedTracksRepository: TopPlayedRepository
) : Repository {
override suspend fun allAlbums(): List<Album> = AlbumLoader.getAllAlbums(context)
override suspend fun allAlbums(): List<Album> = albumRepository.albums()
override suspend fun albumById(albumId: Int): Album = AlbumLoader.getAlbum(context, albumId)
override suspend fun albumById(albumId: Int): Album = albumRepository.album(albumId)
override suspend fun allArtists(): List<Artist> = ArtistLoader.getAllArtists(context)
override suspend fun allArtists(): List<Artist> = artistRepository.artists()
override suspend fun artistById(artistId: Int): Artist =
ArtistLoader.getArtist(context, artistId)
override suspend fun artistById(artistId: Int): Artist = artistRepository.artist(artistId)
override suspend fun recentArtists(): List<Artist> =
LastAddedSongsLoader.getLastAddedArtists(context)
override suspend fun recentArtists(): List<Artist> = lastAddedRepository.recentArtists()
override suspend fun topArtists(): List<Artist> =
TopAndRecentlyPlayedTracksLoader.getTopArtists(context)
override suspend fun recentAlbums(): List<Album> = lastAddedRepository.recentAlbums()
override suspend fun topAlbums(): List<Album> =
TopAndRecentlyPlayedTracksLoader.getTopAlbums(context)
override suspend fun topArtists(): List<Artist> = playedTracksRepository.topArtists()
override suspend fun recentAlbums(): List<Album> =
LastAddedSongsLoader.getLastAddedAlbums(context)
override suspend fun topAlbums(): List<Album> = playedTracksRepository.topAlbums()
override suspend fun allPlaylists(): List<Playlist> = PlaylistLoader.getAllPlaylists(context)
override suspend fun allPlaylists(): List<Playlist> = playlistRepository.playlists()
override suspend fun allGenres(): List<Genre> = GenreLoader.getAllGenres(context)
override suspend fun allGenres(): List<Genre> = genreRepository.genres()
override suspend fun allSongs(): List<Song> = SongLoader.getAllSongs(context)
override suspend fun allSongs(): List<Song> = songRepository.songs()
override suspend fun search(query: String?): MutableList<Any> =
SearchLoader.searchAll(context, query)
searchRepository.searchAll(context, query)
override suspend fun getPlaylistSongs(playlist: Playlist): ArrayList<Song> {
override suspend fun getPlaylistSongs(playlist: Playlist): List<Song> {
return if (playlist is AbsCustomPlaylist) {
playlist.getSongs(context)
playlist.songs()
} else {
PlaylistSongsLoader.getPlaylistSongList(context, playlist.id)
}
}
override suspend fun getGenre(genreId: Int): ArrayList<Song> =
GenreLoader.getSongs(context, genreId)
override suspend fun getGenre(genreId: Int): List<Song> = genreRepository.songs(genreId)
override suspend fun artistInfo(
@ -137,13 +202,16 @@ class RepositoryImpl(
}
suspend fun playlists(): Home {
val playlist = PlaylistLoader.getAllPlaylists(context)
val playlist = playlistRepository.playlists()
return Home(playlist, TOP_ALBUMS)
}
suspend fun playlists(playlistId: Int) =
playlistRepository.playlist(playlistId)
override suspend fun suggestionsHome(): Home {
val songs =
NotRecentlyPlayedPlaylist(context).getSongs(context).shuffled().takeIf {
NotPlayedPlaylist(context).songs().shuffled().takeIf {
it.size > 9
} ?: emptyList()
println(songs.size)
@ -151,33 +219,34 @@ class RepositoryImpl(
}
override suspend fun genresHome(): Home {
val genres = GenreLoader.getAllGenres(context).shuffled()
val genres = genreRepository.genres().shuffled()
return Home(genres, GENRES)
}
override suspend fun recentArtistsHome(): Home {
val artists = LastAddedSongsLoader.getLastAddedArtists(context).take(5)
val artists = lastAddedRepository.recentArtists().take(5)
return Home(artists, RECENT_ARTISTS)
}
override suspend fun recentAlbumsHome(): Home {
val albums = LastAddedSongsLoader.getLastAddedAlbums(context).take(5)
val albums = lastAddedRepository.recentAlbums().take(5)
return Home(albums, RECENT_ALBUMS)
}
override suspend fun topAlbumsHome(): Home {
val albums = TopAndRecentlyPlayedTracksLoader.getTopAlbums(context).take(5)
val albums = playedTracksRepository.topAlbums().take(5)
return Home(albums, TOP_ALBUMS)
}
override suspend fun topArtistsHome(): Home {
val artists = TopAndRecentlyPlayedTracksLoader.getTopArtists(context).take(5)
val artists = playedTracksRepository.topArtists().take(5)
return Home(artists, TOP_ARTISTS)
}
override suspend fun favoritePlaylistHome(): Home {
val playlists = PlaylistLoader.getFavoritePlaylist(context).take(5)
val playlists =
playlistRepository.favoritePlaylist(context.getString(R.string.favorites)).take(5)
val songs = if (playlists.isNotEmpty())
PlaylistSongsLoader.getPlaylistSongList(context, playlists[0])
else emptyList<Song>()
@ -187,7 +256,7 @@ class RepositoryImpl(
override fun songsFlow(): Flow<Result<List<Song>>> = flow {
emit(Result.Loading)
val data = SongLoader.getAllSongs(context)
val data = songRepository.songs()
if (data.isEmpty()) {
emit(Result.Error)
} else {
@ -197,7 +266,7 @@ class RepositoryImpl(
override fun albumsFlow(): Flow<Result<List<Album>>> = flow {
emit(Result.Loading)
val data = AlbumLoader.getAllAlbums(context)
val data = albumRepository.albums()
if (data.isEmpty()) {
emit(Result.Error)
} else {
@ -207,7 +276,7 @@ class RepositoryImpl(
override fun artistsFlow(): Flow<Result<List<Artist>>> = flow {
emit(Result.Loading)
val data = ArtistLoader.getAllArtists(context)
val data = artistRepository.artists()
if (data.isEmpty()) {
emit(Result.Error)
} else {
@ -217,7 +286,7 @@ class RepositoryImpl(
override fun playlistsFlow(): Flow<Result<List<Playlist>>> = flow {
emit(Result.Loading)
val data = PlaylistLoader.getAllPlaylists(context)
val data = playlistRepository.playlists()
if (data.isEmpty()) {
emit(Result.Error)
} else {
@ -227,11 +296,14 @@ class RepositoryImpl(
override fun genresFlow(): Flow<Result<List<Genre>>> = flow {
emit(Result.Loading)
val data = GenreLoader.getAllGenres(context)
val data = genreRepository.genres()
if (data.isEmpty()) {
emit(Result.Error)
} else {
emit(Result.Success(data))
}
}
override suspend fun playlist(playlistId: Int): Playlist =
playlistRepository.playlist(playlistId)
}

View File

@ -12,36 +12,40 @@
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders
package code.name.monkey.retromusic.repository
import android.content.Context
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Genre
import java.util.*
object SearchLoader {
class RealSearchRepository(
private val songRepository: SongRepository,
private val albumRepository: AlbumRepository,
private val artistRepository: RealArtistRepository,
private val genreRepository: GenreRepository,
private val playlistRepository: PlaylistRepository
) {
fun searchAll(context: Context, query: String?): MutableList<Any> {
val results = mutableListOf<Any>()
query?.let { searchString ->
val songs = SongLoader.getSongs(context, searchString)
val songs = songRepository.songs(searchString)
if (songs.isNotEmpty()) {
results.add(context.resources.getString(R.string.songs))
results.addAll(songs)
}
val artists = ArtistLoader.getArtists(context, searchString)
val artists = artistRepository.artists(searchString)
if (artists.isNotEmpty()) {
results.add(context.resources.getString(R.string.artists))
results.addAll(artists)
}
val albums = AlbumLoader.getAlbums(context, searchString)
val albums = albumRepository.albums(searchString)
if (albums.isNotEmpty()) {
results.add(context.resources.getString(R.string.albums))
results.addAll(albums)
}
val genres: List<Genre> = GenreLoader.searchGenres(context)
.filter { genre ->
val genres: List<Genre> = genreRepository.genres().filter { genre ->
genre.name.toLowerCase(Locale.getDefault())
.contains(searchString.toLowerCase(Locale.getDefault()))
}
@ -49,8 +53,7 @@ object SearchLoader {
results.add(context.resources.getString(R.string.genres))
results.addAll(genres)
}
val playlist = PlaylistLoader.getAllPlaylists(context)
.filter { playlist ->
val playlist = playlistRepository.playlists().filter { playlist ->
playlist.name.toLowerCase(Locale.getDefault())
.contains(searchString.toLowerCase(Locale.getDefault()))
}

View File

@ -12,7 +12,7 @@
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders
package code.name.monkey.retromusic.repository
import android.content.Context
import android.database.Cursor
@ -29,55 +29,63 @@ import java.util.*
/**
* Created by hemanths on 10/08/17.
*/
interface SongRepository {
object SongLoader {
fun songs(): List<Song>
fun getAllSongs(
context: Context
): ArrayList<Song> {
val cursor = makeSongCursor(context, null, null)
return getSongs(cursor)
fun songs(cursor: Cursor?): List<Song>
fun songs(query: String): List<Song>
fun songsByFilePath(filePath: String): List<Song>
fun song(cursor: Cursor?): Song
fun song(songId: Int): Song
}
fun getSongs(
cursor: Cursor?
): ArrayList<Song> {
class RealSongRepository(private val context: Context) : SongRepository {
override fun songs(): List<Song> {
return songs(makeSongCursor(null, null))
}
override fun songs(cursor: Cursor?): List<Song> {
val songs = arrayListOf<Song>()
if (cursor != null && cursor.moveToFirst()) {
do {
songs.add(getSongFromCursorImpl(cursor))
} while (cursor.moveToNext())
}
cursor?.close()
return songs
}
fun getSongs(
context: Context,
query: String
): ArrayList<Song> {
val cursor = makeSongCursor(context, AudioColumns.TITLE + " LIKE ?", arrayOf("%$query%"))
return getSongs(cursor)
}
fun getSong(
cursor: Cursor?
): Song {
val song: Song
if (cursor != null && cursor.moveToFirst()) {
song = getSongFromCursorImpl(cursor)
override fun song(cursor: Cursor?): Song {
val song: Song = if (cursor != null && cursor.moveToFirst()) {
getSongFromCursorImpl(cursor)
} else {
song = Song.emptySong
Song.emptySong
}
cursor?.close()
return song
}
@JvmStatic
fun getSong(context: Context, queryId: Int): Song {
val cursor = makeSongCursor(context, AudioColumns._ID + "=?", arrayOf(queryId.toString()))
return getSong(cursor)
override fun songs(query: String): List<Song> {
return songs(makeSongCursor(AudioColumns.TITLE + " LIKE ?", arrayOf("%$query%")))
}
override fun song(songId: Int): Song {
return song(makeSongCursor(AudioColumns._ID + "=?", arrayOf(songId.toString())))
}
override fun songsByFilePath(filePath: String): List<Song> {
return songs(
makeSongCursor(
MediaStore.Audio.AudioColumns.DATA + "=?",
arrayOf(filePath)
)
)
}
private fun getSongFromCursorImpl(
@ -115,7 +123,6 @@ object SongLoader {
@JvmOverloads
fun makeSongCursor(
context: Context,
selection: String?,
selectionValues: Array<String>?,
sortOrder: String = PreferenceUtil.songSortOrder
@ -131,8 +138,16 @@ object SongLoader {
// Blacklist
val paths = BlacklistStore.getInstance(context).paths
if (paths.isNotEmpty()) {
selectionFinal = generateBlacklistSelection(selectionFinal, paths.size)
selectionValuesFinal = addBlacklistSelectionValues(selectionValuesFinal, paths)
selectionFinal =
generateBlacklistSelection(
selectionFinal,
paths.size
)
selectionValuesFinal =
addBlacklistSelectionValues(
selectionValuesFinal,
paths
)
}
selectionFinal =
selectionFinal + " AND " + MediaStore.Audio.Media.DURATION + ">= " + (PreferenceUtil.filterLength * 1000)

View File

@ -11,7 +11,7 @@
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders;
package code.name.monkey.retromusic.repository;
import android.database.AbstractCursor;
import android.database.Cursor;

View File

@ -11,7 +11,7 @@
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders;
package code.name.monkey.retromusic.repository;
import android.database.AbstractCursor;
import android.database.Cursor;

View File

@ -12,59 +12,85 @@
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.loaders
package code.name.monkey.retromusic.repository
import android.content.Context
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.loaders.SongLoader.makeSongCursor
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.providers.HistoryStore
import code.name.monkey.retromusic.providers.SongPlayCountStore
import code.name.monkey.retromusic.util.PreferenceUtil
import java.util.*
/**
* Created by hemanths on 16/08/17.
*/
object TopAndRecentlyPlayedTracksLoader {
interface TopPlayedRepository {
fun recentlyPlayedTracks(): List<Song>
fun getRecentlyPlayedTracks(context: Context): ArrayList<Song> {
return SongLoader.getSongs(makeRecentTracksCursorAndClearUpDatabase(context))
fun topTracks(): List<Song>
fun notRecentlyPlayedTracks(): List<Song>
fun topAlbums(): List<Album>
fun topArtists(): List<Artist>
}
fun getTopTracks(context: Context): ArrayList<Song> {
return SongLoader.getSongs(makeTopTracksCursorAndClearUpDatabase(context))
class RealTopPlayedRepository(
private val context: Context,
private val songRepository: RealSongRepository,
private val albumRepository: RealAlbumRepository,
private val artistRepository: RealArtistRepository
) : TopPlayedRepository {
override fun recentlyPlayedTracks(): List<Song> {
return songRepository.songs(makeRecentTracksCursorAndClearUpDatabase())
}
fun getNotRecentlyPlayedTracks(context: Context): ArrayList<Song> {
val allSongs = SongLoader.getSongs(
makeSongCursor(
context,
override fun topTracks(): List<Song> {
return songRepository.songs(makeTopTracksCursorAndClearUpDatabase())
}
override fun notRecentlyPlayedTracks(): List<Song> {
val allSongs = mutableListOf<Song>().apply {
addAll(
songRepository.songs(
songRepository.makeSongCursor(
null, null,
MediaStore.Audio.Media.DATE_ADDED + " ASC"
)
)
val playedSongs = SongLoader.getSongs(
makePlayedTracksCursorAndClearUpDatabase(context)
)
val notRecentlyPlayedSongs = SongLoader.getSongs(
makeNotRecentTracksCursorAndClearUpDatabase(context)
}
val playedSongs = songRepository.songs(
makePlayedTracksCursorAndClearUpDatabase()
)
val notRecentlyPlayedSongs = songRepository.songs(
makeNotRecentTracksCursorAndClearUpDatabase()
)
allSongs.removeAll(playedSongs)
allSongs.addAll(notRecentlyPlayedSongs)
return allSongs
}
private fun makeTopTracksCursorAndClearUpDatabase(context: Context): Cursor? {
val retCursor = makeTopTracksCursorImpl(context)
override fun topAlbums(): List<Album> {
return albumRepository.splitIntoAlbums(topTracks())
}
override fun topArtists(): List<Artist> {
return artistRepository.splitIntoArtists(topAlbums())
}
private fun makeTopTracksCursorAndClearUpDatabase(): Cursor? {
val retCursor = makeTopTracksCursorImpl()
// clean up the databases with any ids not found
if (retCursor != null) {
val missingIds = retCursor.missingIds
@ -77,33 +103,31 @@ object TopAndRecentlyPlayedTracksLoader {
return retCursor
}
private fun makeRecentTracksCursorImpl(context: Context): SortedLongCursor? {
private fun makeRecentTracksCursorImpl(): SortedLongCursor? {
// first get the top results ids from the internal database
val songs = HistoryStore.getInstance(context).queryRecentIds()
songs.use {
return makeSortedCursor(
context,
it,
it.getColumnIndex(HistoryStore.RecentStoreColumns.ID)
)
}
}
private fun makeTopTracksCursorImpl(context: Context): SortedLongCursor? {
private fun makeTopTracksCursorImpl(): SortedLongCursor? {
// first get the top results ids from the internal database
val songs =
SongPlayCountStore.getInstance(context).getTopPlayedResults(NUMBER_OF_TOP_TRACKS)
songs.use { localSongs ->
return makeSortedCursor(
context, localSongs,
localSongs,
localSongs.getColumnIndex(SongPlayCountStore.SongPlayCountColumns.ID)
)
}
}
private fun makeSortedCursor(
context: Context,
cursor: Cursor?, idColumn: Int
): SortedLongCursor? {
@ -131,48 +155,46 @@ object TopAndRecentlyPlayedTracksLoader {
selection.append(")")
// get a list of songs with the data given the selection statement
val songCursor = SongLoader.makeSongCursor(context, selection.toString(), null)
val songCursor = songRepository.makeSongCursor(selection.toString(), null)
if (songCursor != null) {
// now return the wrapped TopTracksCursor to handle sorting given order
return SortedLongCursor(songCursor, order, BaseColumns._ID)
return SortedLongCursor(
songCursor,
order,
BaseColumns._ID
)
}
}
return null
}
fun getTopAlbums(
context: Context
): ArrayList<Album> {
arrayListOf<Album>()
return AlbumLoader.splitIntoAlbums(getTopTracks(context))
private fun makeRecentTracksCursorAndClearUpDatabase(): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(
ignoreCutoffTime = false,
reverseOrder = false
)
}
fun getTopArtists(context: Context): ArrayList<Artist> {
return ArtistLoader.splitIntoArtists(getTopAlbums(context))
private fun makePlayedTracksCursorAndClearUpDatabase(): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(
ignoreCutoffTime = true,
reverseOrder = false
)
}
fun makeRecentTracksCursorAndClearUpDatabase(context: Context): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(context, false, false)
}
fun makePlayedTracksCursorAndClearUpDatabase(context: Context): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(context, true, false)
}
fun makeNotRecentTracksCursorAndClearUpDatabase(context: Context): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(context, false, true)
private fun makeNotRecentTracksCursorAndClearUpDatabase(): Cursor? {
return makeRecentTracksCursorAndClearUpDatabaseImpl(
ignoreCutoffTime = false,
reverseOrder = true
)
}
private fun makeRecentTracksCursorAndClearUpDatabaseImpl(
context: Context,
ignoreCutoffTime: Boolean,
reverseOrder: Boolean
): SortedLongCursor? {
val retCursor = makeRecentTracksCursorImpl(context, ignoreCutoffTime, reverseOrder)
val retCursor = makeRecentTracksCursorImpl(ignoreCutoffTime, reverseOrder)
// clean up the databases with any ids not found
// clean up the databases with any ids not found
if (retCursor != null) {
@ -186,9 +208,7 @@ object TopAndRecentlyPlayedTracksLoader {
return retCursor
}
private fun makeRecentTracksCursorImpl(
context: Context,
ignoreCutoffTime: Boolean,
reverseOrder: Boolean
): SortedLongCursor? {
@ -198,7 +218,6 @@ object TopAndRecentlyPlayedTracksLoader {
HistoryStore.getInstance(context).queryRecentIds(cutoff * if (reverseOrder) -1 else 1)
return songs.use {
makeSortedCursor(
context,
it,
it.getColumnIndex(HistoryStore.RecentStoreColumns.ID)
)

View File

@ -327,7 +327,7 @@ public class MusicService extends Service implements
}
private static String getTrackUri(@NonNull Song song) {
return MusicUtil.getSongFileUri(song.getId()).toString();
return MusicUtil.INSTANCE.getSongFileUri(song.getId()).toString();
}
@Override
@ -790,7 +790,7 @@ public class MusicService extends Service implements
pendingQuit = true;
break;
case TOGGLE_FAVORITE:
MusicUtil.toggleFavorite(getApplicationContext(), getCurrentSong());
MusicUtil.INSTANCE.toggleFavorite(getApplicationContext(), getCurrentSong());
break;
}
}
@ -1216,7 +1216,7 @@ public class MusicService extends Service implements
Playlist playlist = intent.getParcelableExtra(INTENT_EXTRA_PLAYLIST);
int shuffleMode = intent.getIntExtra(INTENT_EXTRA_SHUFFLE_MODE, getShuffleMode());
if (playlist != null) {
ArrayList<Song> playlistSongs = playlist.getSongs(getApplicationContext());
List<Song> playlistSongs = playlist.getSongs();
if (!playlistSongs.isEmpty()) {
if (shuffleMode == SHUFFLE_MODE_SHUFFLE) {
int startPosition = new Random().nextInt(playlistSongs.size());
@ -1329,7 +1329,7 @@ public class MusicService extends Service implements
TOGGLE_SHUFFLE, getString(R.string.action_toggle_shuffle), shuffleIcon)
.build());
final int favoriteIcon = MusicUtil.isFavorite(getApplicationContext(), getCurrentSong())
final int favoriteIcon = MusicUtil.INSTANCE.isFavorite(getApplicationContext(), getCurrentSong())
? R.drawable.ic_favorite : R.drawable.ic_favorite_border;
stateBuilder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder(
TOGGLE_FAVORITE, getString(R.string.action_toggle_favorite), favoriteIcon)

View File

@ -172,7 +172,7 @@ public class AutoGeneratedPlaylistBitmap {
private static Bitmap getBitmapWithAlbumId(@NonNull Context context, Integer id) {
try {
return Glide.with(context)
.load(MusicUtil.getMediaStoreAlbumCoverUri(id))
.load(MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(id))
.asBitmap()
.into(200, 200)
.get();

View File

@ -31,15 +31,14 @@ import java.io.FileInputStream;
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.LinkedList;
import java.util.List;
import code.name.monkey.retromusic.loaders.SongLoader;
import code.name.monkey.retromusic.loaders.SortedCursor;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.repository.RealSongRepository;
import code.name.monkey.retromusic.repository.SortedCursor;
public final class FileUtil {
@ -59,9 +58,9 @@ public final class FileUtil {
}
@NonNull
public static ArrayList<Song> matchFilesWithMediaStore(@NonNull Context context,
public static List<Song> matchFilesWithMediaStore(@NonNull Context context,
@Nullable List<File> files) {
return SongLoader.INSTANCE.getSongs(makeSongCursor(context, files));
return new RealSongRepository(context).songs(makeSongCursor(context, files));
}
public static String safeGetCanonicalPath(File file) {
@ -89,7 +88,7 @@ public final class FileUtil {
}
}
Cursor songCursor = SongLoader.INSTANCE.makeSongCursor(context, selection, selection == null ? null : paths);
Cursor songCursor = new RealSongRepository(context).makeSongCursor(selection, selection == null ? null : paths);
return songCursor == null ? null
: new SortedCursor(songCursor, paths, MediaStore.Audio.AudioColumns.DATA);

View File

@ -1,428 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.util;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.tag.FieldKey;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.helper.MusicPlayerRemote;
import code.name.monkey.retromusic.loaders.PlaylistLoader;
import code.name.monkey.retromusic.loaders.SongLoader;
import code.name.monkey.retromusic.model.Artist;
import code.name.monkey.retromusic.model.Playlist;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics;
import code.name.monkey.retromusic.service.MusicService;
public class MusicUtil {
public static final String TAG = MusicUtil.class.getSimpleName();
private static Playlist playlist;
/**
* Build a concatenated string from the provided arguments
* The intended purpose is to show extra annotations
* to a music library item.
* Ex: for a given album --> buildInfoString(album.artist, album.songCount)
*/
@NonNull
public static String buildInfoString(@Nullable final String string1, @Nullable final String string2) {
// Skip empty strings
if (TextUtils.isEmpty(string1)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string2) ? "" : string2;
}
if (TextUtils.isEmpty(string2)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string1) ? "" : string1;
}
return string1 + " • " + string2;
}
@NonNull
public static File createAlbumArtFile() {
return new File(createAlbumArtDir(), String.valueOf(System.currentTimeMillis()));
}
@NonNull
public static Intent createShareSongFileIntent(@NonNull final Song song, @NonNull Context context) {
try {
return new Intent()
.setAction(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_STREAM, FileProvider
.getUriForFile(context, context.getApplicationContext().getPackageName(),
new File(song.getData())))
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setType("audio/*");
} catch (IllegalArgumentException e) {
// TODO the path is most likely not like /storage/emulated/0/... but something like /storage/28C7-75B0/...
e.printStackTrace();
Toast.makeText(context, "Could not share this file, I'm aware of the issue.", Toast.LENGTH_SHORT).show();
return new Intent();
}
}
public static void deleteAlbumArt(@NonNull Context context, int albumId) {
ContentResolver contentResolver = context.getContentResolver();
Uri localUri = Uri.parse("content://media/external/audio/albumart");
contentResolver.delete(ContentUris.withAppendedId(localUri, albumId), null, null);
contentResolver.notifyChange(localUri, null);
}
public static void deleteTracks(
@NonNull final Activity activity,
@NonNull final List<Song> songs,
@Nullable final List<Uri> safUris,
@Nullable final Runnable callback) {
final String[] projection = new String[]{
BaseColumns._ID, MediaStore.MediaColumns.DATA
};
// Split the query into multiple batches, and merge the resulting cursors
int batchStart = 0;
int batchEnd = 0;
final int batchSize = 1000000
/ 10; // 10^6 being the SQLite limite on the query lenth in bytes, 10 being the max number of digits in an int, used to store the track ID
final int songCount = songs.size();
while (batchEnd < songCount) {
batchStart = batchEnd;
final StringBuilder selection = new StringBuilder();
selection.append(BaseColumns._ID + " IN (");
for (int i = 0; (i < batchSize - 1) && (batchEnd < songCount - 1); i++, batchEnd++) {
selection.append(songs.get(batchEnd).getId());
selection.append(",");
}
// The last element of a batch
selection.append(songs.get(batchEnd).getId());
batchEnd++;
selection.append(")");
try {
final Cursor cursor = activity.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
null, null);
// TODO: At this point, there is no guarantee that the size of the cursor is the same as the size of the selection string.
// Despite that, the Step 3 assumes that the safUris elements are tracking closely the content of the cursor.
if (cursor != null) {
// Step 1: Remove selected tracks from the current playlist, as well
// as from the album art cache
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
final int id = cursor.getInt(0);
final Song song = SongLoader.getSong(activity, id);
MusicPlayerRemote.removeFromQueue(song);
cursor.moveToNext();
}
// Step 2: Remove selected tracks from the database
activity.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
selection.toString(), null);
// Step 3: Remove files from card
cursor.moveToFirst();
int i = batchStart;
while (!cursor.isAfterLast()) {
final String name = cursor.getString(1);
final Uri safUri = safUris == null || safUris.size() <= i ? null : safUris.get(i);
SAFUtil.delete(activity, name, safUri);
i++;
cursor.moveToNext();
}
cursor.close();
}
} catch (SecurityException ignored) {
}
}
activity.getContentResolver().notifyChange(Uri.parse("content://media"), null);
activity.runOnUiThread(() -> {
Toast.makeText(activity, activity.getString(R.string.deleted_x_songs, songCount), Toast.LENGTH_SHORT)
.show();
if (callback != null) {
callback.run();
}
});
}
@NonNull
public static String getArtistInfoString(@NonNull final Context context,
@NonNull final Artist artist) {
int albumCount = artist.getAlbumCount();
int songCount = artist.getSongCount();
String albumString = albumCount == 1 ? context.getResources().getString(R.string.album)
: context.getResources().getString(R.string.albums);
String songString = songCount == 1 ? context.getResources().getString(R.string.song)
: context.getResources().getString(R.string.songs);
return albumCount + " " + albumString + " • " + songCount + " " + songString;
}
//iTunes uses for example 1002 for track 2 CD1 or 3011 for track 11 CD3.
//this method converts those values to normal tracknumbers
public static int getFixedTrackNumber(int trackNumberToFix) {
return trackNumberToFix % 1000;
}
@Nullable
public static String getLyrics(@NonNull Song song) {
String lyrics = null;
File file = new File(song.getData());
try {
lyrics = AudioFileIO.read(file).getTagOrCreateDefault().getFirst(FieldKey.LYRICS);
} catch (Exception e) {
e.printStackTrace();
}
if (lyrics == null || lyrics.trim().isEmpty() || !AbsSynchronizedLyrics
.isSynchronized(lyrics)) {
File dir = file.getAbsoluteFile().getParentFile();
if (dir != null && dir.exists() && dir.isDirectory()) {
String format = ".*%s.*\\.(lrc|txt)";
String filename = Pattern.quote(FileUtil.stripExtension(file.getName()));
String songtitle = Pattern.quote(song.getTitle());
final ArrayList<Pattern> patterns = new ArrayList<>();
patterns.add(Pattern.compile(String.format(format, filename),
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE));
patterns.add(Pattern.compile(String.format(format, songtitle),
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE));
File[] files = dir.listFiles(f -> {
for (Pattern pattern : patterns) {
if (pattern.matcher(f.getName()).matches()) {
return true;
}
}
return false;
});
if (files != null && files.length > 0) {
for (File f : files) {
try {
String newLyrics = FileUtil.read(f);
if (newLyrics != null && !newLyrics.trim().isEmpty()) {
if (AbsSynchronizedLyrics.isSynchronized(newLyrics)) {
return newLyrics;
}
lyrics = newLyrics;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
return lyrics;
}
@NonNull
public static Uri getMediaStoreAlbumCoverUri(int albumId) {
final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
return ContentUris.withAppendedId(sArtworkUri, albumId);
}
@NonNull
public static Playlist getPlaylist() {
return playlist;
}
public static void setPlaylist(@NonNull Playlist playlist) {
MusicUtil.playlist = playlist;
}
@NonNull
public static String getPlaylistInfoString(@NonNull final Context context, @NonNull List<Song> songs) {
final long duration = getTotalDuration(songs);
return MusicUtil.buildInfoString(
MusicUtil.getSongCountString(context, songs.size()),
MusicUtil.getReadableDurationString(duration)
);
}
public static String getReadableDurationString(long songDurationMillis) {
long minutes = (songDurationMillis / 1000) / 60;
long seconds = (songDurationMillis / 1000) % 60;
if (minutes < 60) {
return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);
} else {
long hours = minutes / 60;
minutes = minutes % 60;
return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds);
}
}
@NonNull
public static String getSectionName(@Nullable String musicMediaTitle) {
try {
if (TextUtils.isEmpty(musicMediaTitle)) {
return "";
}
musicMediaTitle = musicMediaTitle.trim().toLowerCase();
if (musicMediaTitle.startsWith("the ")) {
musicMediaTitle = musicMediaTitle.substring(4);
} else if (musicMediaTitle.startsWith("a ")) {
musicMediaTitle = musicMediaTitle.substring(2);
}
if (musicMediaTitle.isEmpty()) {
return "";
}
return musicMediaTitle.substring(0, 1).toUpperCase();
} catch (Exception e) {
return "";
}
}
@NonNull
public static String getSongCountString(@NonNull final Context context, int songCount) {
final String songString = songCount == 1 ? context.getResources().getString(R.string.song)
: context.getResources().getString(R.string.songs);
return songCount + " " + songString;
}
public static Uri getSongFileUri(int songId) {
return ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, songId);
}
public static long getTotalDuration(@NonNull List<Song> songs) {
long duration = 0;
for (int i = 0; i < songs.size(); i++) {
duration += songs.get(i).getDuration();
}
return duration;
}
@NonNull
public static String getYearString(int year) {
return year > 0 ? String.valueOf(year) : "-";
}
public static int indexOfSongInList(@NonNull List<Song> songs, int songId) {
for (int i = 0; i < songs.size(); i++) {
if (songs.get(i).getId() == songId) {
return i;
}
}
return -1;
}
public static void insertAlbumArt(@NonNull Context context, int albumId, String path) {
ContentResolver contentResolver = context.getContentResolver();
Uri artworkUri = Uri.parse("content://media/external/audio/albumart");
contentResolver.delete(ContentUris.withAppendedId(artworkUri, albumId), null, null);
ContentValues values = new ContentValues();
values.put("album_id", albumId);
values.put("_data", path);
contentResolver.insert(artworkUri, values);
contentResolver.notifyChange(artworkUri, null);
}
public static boolean isArtistNameUnknown(@Nullable String artistName) {
if (TextUtils.isEmpty(artistName)) {
return false;
}
if (artistName.equals(Artist.UNKNOWN_ARTIST_DISPLAY_NAME)) {
return true;
}
String tempName = artistName.trim().toLowerCase();
return tempName.equals("unknown") || tempName.equals("<unknown>");
}
public static boolean isFavorite(@NonNull final Context context, @NonNull final Song song) {
return PlaylistsUtil
.doPlaylistContains(context, getFavoritesPlaylist(context).id, song.getId());
}
public static boolean isFavoritePlaylist(@NonNull final Context context,
@NonNull final Playlist playlist) {
return playlist.name != null && playlist.name.equals(context.getString(R.string.favorites));
}
public static void toggleFavorite(@NonNull final Context context, @NonNull final Song song) {
if (isFavorite(context, song)) {
PlaylistsUtil.removeFromPlaylist(context, song, getFavoritesPlaylist(context).id);
} else {
PlaylistsUtil.addToPlaylist(context, song, getOrCreateFavoritesPlaylist(context).id,
false);
}
context.sendBroadcast(new Intent(MusicService.FAVORITE_STATE_CHANGED));
}
@NonNull
@SuppressWarnings("ResultOfMethodCallIgnored")
private static File createAlbumArtDir() {
File albumArtDir = new File(Environment.getExternalStorageDirectory(), "/albumthumbs/");
if (!albumArtDir.exists()) {
albumArtDir.mkdirs();
try {
new File(albumArtDir, ".nomedia").createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
return albumArtDir;
}
private static Playlist getFavoritesPlaylist(@NonNull final Context context) {
return PlaylistLoader.INSTANCE.getPlaylist(context, context.getString(R.string.favorites));
}
private static Playlist getOrCreateFavoritesPlaylist(@NonNull final Context context) {
return PlaylistLoader.INSTANCE.getPlaylist(context,
PlaylistsUtil.createPlaylist(context, context.getString(R.string.favorites)));
}
}

View File

@ -0,0 +1,418 @@
package code.name.monkey.retromusic.util
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Environment
import android.provider.BaseColumns
import android.provider.MediaStore
import android.text.TextUtils
import android.widget.Toast
import androidx.core.content.FileProvider
import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote.removeFromQueue
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics
import code.name.monkey.retromusic.repository.RealPlaylistRepository
import code.name.monkey.retromusic.repository.SongRepository
import code.name.monkey.retromusic.service.MusicService
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey
import org.koin.core.KoinComponent
import org.koin.core.get
import java.io.File
import java.io.IOException
import java.util.*
import java.util.regex.Pattern
object MusicUtil : KoinComponent {
fun createShareSongFileIntent(song: Song, context: Context): Intent? {
return try {
Intent().setAction(Intent.ACTION_SEND).putExtra(
Intent.EXTRA_STREAM,
FileProvider.getUriForFile(
context,
context.applicationContext.packageName,
File(song.data)
)
).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION).setType("audio/*")
} catch (e: IllegalArgumentException) {
// TODO the path is most likely not like /storage/emulated/0/... but something like /storage/28C7-75B0/...
e.printStackTrace()
Toast.makeText(
context,
"Could not share this file, I'm aware of the issue.",
Toast.LENGTH_SHORT
).show()
Intent()
}
}
fun buildInfoString(string1: String?, string2: String?): String {
if (string1.isNullOrEmpty()) {
return if (string2.isNullOrEmpty()) "" else string2
}
return if (string2.isNullOrEmpty()) if (string1.isNullOrEmpty()) "" else string1 else "$string1$string2"
}
fun createAlbumArtFile(): File {
return File(
createAlbumArtDir(),
System.currentTimeMillis().toString()
)
}
private fun createAlbumArtDir(): File {
val albumArtDir = File(Environment.getExternalStorageDirectory(), "/albumthumbs/")
if (!albumArtDir.exists()) {
albumArtDir.mkdirs()
try {
File(albumArtDir, ".nomedia").createNewFile()
} catch (e: IOException) {
e.printStackTrace()
}
}
return albumArtDir
}
fun deleteAlbumArt(context: Context, albumId: Int) {
val contentResolver = context.contentResolver
val localUri = Uri.parse("content://media/external/audio/albumart")
contentResolver.delete(ContentUris.withAppendedId(localUri, albumId.toLong()), null, null)
contentResolver.notifyChange(localUri, null)
}
fun getArtistInfoString(
context: Context,
artist: Artist
): String {
val albumCount = artist.albumCount
val songCount = artist.songCount
val albumString =
if (albumCount == 1) context.resources.getString(R.string.album)
else context.resources.getString(R.string.albums)
val songString =
if (songCount == 1) context.resources.getString(R.string.song)
else context.resources.getString(R.string.songs)
return "$albumCount $albumString$songCount $songString"
}
//iTunes uses for example 1002 for track 2 CD1 or 3011 for track 11 CD3.
//this method converts those values to normal tracknumbers
fun getFixedTrackNumber(trackNumberToFix: Int): Int {
return trackNumberToFix % 1000
}
fun getLyrics(song: Song): String? {
var lyrics: String? = null
val file = File(song.data)
try {
lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
} catch (e: Exception) {
e.printStackTrace()
}
if (lyrics == null || lyrics.trim { it <= ' ' }.isEmpty() || !AbsSynchronizedLyrics
.isSynchronized(lyrics)
) {
val dir = file.absoluteFile.parentFile
if (dir != null && dir.exists() && dir.isDirectory) {
val format = ".*%s.*\\.(lrc|txt)"
val filename = Pattern.quote(
FileUtil.stripExtension(file.name)
)
val songtitle = Pattern.quote(song.title)
val patterns =
ArrayList<Pattern>()
patterns.add(
Pattern.compile(
String.format(format, filename),
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE
)
)
patterns.add(
Pattern.compile(
String.format(format, songtitle),
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE
)
)
val files =
dir.listFiles { f: File ->
for (pattern in patterns) {
if (pattern.matcher(f.name).matches()) {
return@listFiles true
}
}
false
}
if (files != null && files.size > 0) {
for (f in files) {
try {
val newLyrics =
FileUtil.read(f)
if (newLyrics != null && !newLyrics.trim { it <= ' ' }.isEmpty()) {
if (AbsSynchronizedLyrics.isSynchronized(newLyrics)) {
return newLyrics
}
lyrics = newLyrics
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
return lyrics
}
fun getMediaStoreAlbumCoverUri(albumId: Int): Uri {
val sArtworkUri =
Uri.parse("content://media/external/audio/albumart")
return ContentUris.withAppendedId(sArtworkUri, albumId.toLong())
}
fun getPlaylistInfoString(
context: Context,
songs: List<Song>
): String {
val duration = getTotalDuration(songs)
return buildInfoString(
getSongCountString(context, songs.size),
getReadableDurationString(duration)
)
}
fun getReadableDurationString(songDurationMillis: Long): String? {
var minutes = songDurationMillis / 1000 / 60
val seconds = songDurationMillis / 1000 % 60
return if (minutes < 60) {
String.format(
Locale.getDefault(),
"%02d:%02d",
minutes,
seconds
)
} else {
val hours = minutes / 60
minutes = minutes % 60
String.format(
Locale.getDefault(),
"%02d:%02d:%02d",
hours,
minutes,
seconds
)
}
}
fun getSectionName(musicMediaTitle: String?): String {
var musicMediaTitle = musicMediaTitle
return try {
if (TextUtils.isEmpty(musicMediaTitle)) {
return ""
}
musicMediaTitle = musicMediaTitle!!.trim { it <= ' ' }.toLowerCase()
if (musicMediaTitle.startsWith("the ")) {
musicMediaTitle = musicMediaTitle.substring(4)
} else if (musicMediaTitle.startsWith("a ")) {
musicMediaTitle = musicMediaTitle.substring(2)
}
if (musicMediaTitle.isEmpty()) {
""
} else musicMediaTitle.substring(0, 1).toUpperCase()
} catch (e: Exception) {
""
}
}
fun getSongCountString(context: Context, songCount: Int): String {
val songString = if (songCount == 1) context.resources
.getString(R.string.song) else context.resources.getString(R.string.songs)
return "$songCount $songString"
}
fun getSongFileUri(songId: Int): Uri {
return ContentUris.withAppendedId(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
songId.toLong()
)
}
fun getTotalDuration(songs: List<Song>): Long {
var duration: Long = 0
for (i in songs.indices) {
duration += songs[i].duration
}
return duration
}
fun getYearString(year: Int): String {
return if (year > 0) year.toString() else "-"
}
fun indexOfSongInList(songs: List<Song>, songId: Int): Int {
for (i in songs.indices) {
if (songs[i].id == songId) {
return i
}
}
return -1
}
fun insertAlbumArt(
context: Context,
albumId: Int,
path: String?
) {
val contentResolver = context.contentResolver
val artworkUri =
Uri.parse("content://media/external/audio/albumart")
contentResolver.delete(ContentUris.withAppendedId(artworkUri, albumId.toLong()), null, null)
val values = ContentValues()
values.put("album_id", albumId)
values.put("_data", path)
contentResolver.insert(artworkUri, values)
contentResolver.notifyChange(artworkUri, null)
}
fun isArtistNameUnknown(artistName: String?): Boolean {
if (TextUtils.isEmpty(artistName)) {
return false
}
if (artistName == Artist.UNKNOWN_ARTIST_DISPLAY_NAME) {
return true
}
val tempName = artistName!!.trim { it <= ' ' }.toLowerCase()
return tempName == "unknown" || tempName == "<unknown>"
}
fun isFavorite(context: Context, song: Song): Boolean {
return PlaylistsUtil
.doPlaylistContains(context, getFavoritesPlaylist(context).id.toLong(), song.id)
}
fun isFavoritePlaylist(
context: Context,
playlist: Playlist
): Boolean {
return playlist.name != null && playlist.name == context.getString(R.string.favorites)
}
fun toggleFavorite(context: Context, song: Song) {
if (isFavorite(context, song)) {
PlaylistsUtil.removeFromPlaylist(context, song, getFavoritesPlaylist(context).id)
} else {
PlaylistsUtil.addToPlaylist(
context, song, getOrCreateFavoritesPlaylist(context).id,
false
)
}
context.sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED))
}
private fun getFavoritesPlaylist(context: Context): Playlist {
return RealPlaylistRepository(context.contentResolver).playlist(context.getString(R.string.favorites))
}
private fun getOrCreateFavoritesPlaylist(context: Context): Playlist {
return RealPlaylistRepository(context.contentResolver).playlist(
PlaylistsUtil.createPlaylist(
context,
context.getString(R.string.favorites)
)
)
}
fun deleteTracks(
activity: FragmentActivity,
songs: List<Song>,
safUris: List<Uri>?,
callback: Runnable?
) {
val songRepository: SongRepository = get()
val projection = arrayOf(
BaseColumns._ID, MediaStore.MediaColumns.DATA
)
// Split the query into multiple batches, and merge the resulting cursors
var batchStart = 0
var batchEnd = 0
val batchSize =
1000000 / 10 // 10^6 being the SQLite limite on the query lenth in bytes, 10 being the max number of digits in an int, used to store the track ID
val songCount = songs.size
while (batchEnd < songCount) {
batchStart = batchEnd
val selection = StringBuilder()
selection.append(BaseColumns._ID + " IN (")
var i = 0
while (i < batchSize - 1 && batchEnd < songCount - 1) {
selection.append(songs[batchEnd].id)
selection.append(",")
i++
batchEnd++
}
// The last element of a batch
// The last element of a batch
selection.append(songs[batchEnd].id)
batchEnd++
selection.append(")")
try {
val cursor = activity.contentResolver.query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
null, null
)
if (cursor != null) {
// Step 1: Remove selected tracks from the current playlist, as well
// as from the album art cache
cursor.moveToFirst()
while (!cursor.isAfterLast) {
val id = cursor.getInt(0)
val song: Song = songRepository.song(id)
removeFromQueue(song)
cursor.moveToNext()
}
// Step 2: Remove selected tracks from the database
activity.contentResolver.delete(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
selection.toString(), null
)
// Step 3: Remove files from card
cursor.moveToFirst()
var index = batchStart
while (!cursor.isAfterLast) {
val name = cursor.getString(1)
val safUri =
if (safUris == null || safUris.size <= index) null else safUris[index]
SAFUtil.delete(activity, name, safUri)
index++
cursor.moveToNext()
}
cursor.close()
}
} catch (ignored: SecurityException) {
}
activity.contentResolver.notifyChange(Uri.parse("content://media"), null)
activity.runOnUiThread {
Toast.makeText(
activity,
activity.getString(R.string.deleted_x_songs, songCount),
Toast.LENGTH_SHORT
)
.show()
callback?.run()
}
}
}
}

View File

@ -87,7 +87,7 @@ public class PlaylistsUtil {
final StringBuilder selection = new StringBuilder();
selection.append(MediaStore.Audio.Playlists._ID + " IN (");
for (int i = 0; i < playlists.size(); i++) {
selection.append(playlists.get(i).id);
selection.append(playlists.get(i).getId());
if (i < playlists.size() - 1) {
selection.append(",");
}