🙆🏻 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 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.albums.AlbumDetailsViewModel
import code.name.monkey.retromusic.fragments.artists.ArtistDetailsViewModel import code.name.monkey.retromusic.fragments.artists.ArtistDetailsViewModel
import code.name.monkey.retromusic.fragments.genres.GenreDetailsViewModel import code.name.monkey.retromusic.fragments.genres.GenreDetailsViewModel
import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel
import code.name.monkey.retromusic.fragments.search.SearchViewModel 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.Genre
import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.network.networkModule import code.name.monkey.retromusic.network.networkModule
import code.name.monkey.retromusic.providers.RepositoryImpl import code.name.monkey.retromusic.repository.*
import org.eclipse.egit.github.core.Repository import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.bind import org.koin.dsl.bind
import org.koin.dsl.module import org.koin.dsl.module
private val dataModule = module { private val dataModule = module {
single { single {
RepositoryImpl(get(), get()) RealRepository(get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
} bind Repository::class } 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 { private val viewModules = module {

View File

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

View File

@ -14,6 +14,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.ColorUtil 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.R.drawable
import code.name.monkey.retromusic.activities.base.AbsBaseActivity import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity 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.RetroUtil
import code.name.monkey.retromusic.util.SAFUtil import code.name.monkey.retromusic.util.SAFUtil
import com.google.android.material.button.MaterialButton 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.AudioFile
import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject
import java.io.File import java.io.File
import java.util.* import java.util.*
abstract class AbsTagEditorActivity : AbsBaseActivity() { abstract class AbsTagEditorActivity : AbsBaseActivity() {
val repository by inject<Repository>()
lateinit var saveFab: MaterialButton
protected var id: Int = 0 protected var id: Int = 0
private set private set
private var paletteColorPrimary: Int = 0 private var paletteColorPrimary: Int = 0
private var isInNoImageMode: Boolean = false private var isInNoImageMode: Boolean = false
private var songPaths: List<String>? = null private var songPaths: List<String>? = null
lateinit var saveFab: MaterialButton
private var savedSongPaths: List<String>? = null private var savedSongPaths: List<String>? = null
private val currentSongPath: String? = null private val currentSongPath: String? = null
private var savedTags: Map<FieldKey, String>? = null private var savedTags: Map<FieldKey, String>? = null
@ -172,33 +175,27 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(contentViewLayout) setContentView(contentViewLayout)
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
saveFab = findViewById(R.id.saveTags) saveFab = findViewById(R.id.saveTags)
getIntentExtras() getIntentExtras()
lifecycleScope.launchWhenCreated {
songPaths = getSongPaths() songPaths = getSongPaths()
if (songPaths!!.isEmpty()) { if (songPaths!!.isEmpty()) {
finish() finish()
return
} }
}
setUpViews() setUpViews()
setStatusbarColorAuto()
setNavigationbarColorAuto()
setTaskDescriptionColorAuto()
} }
private fun setUpViews() { private fun setUpViews() {
setUpScrollView()
setUpFab() setUpFab()
setUpImageView() setUpImageView()
} }
private fun setUpScrollView() {
//observableScrollView.setScrollViewCallbacks(observableScrollViewCallbacks);
}
private lateinit var items: List<String> private lateinit var items: List<String>
private fun setUpImageView() { 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) { protected fun searchWebFor(vararg keys: String) {
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()
@ -336,7 +333,7 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() {
hideFab() hideFab()
savedSongPaths = getSongPaths() savedSongPaths = songPaths
savedTags = fieldKeyValueMap savedTags = fieldKeyValueMap
savedArtworkInfo = artworkInfo 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.extensions.appHandleColor
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper 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.ImageUtil
import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette
import code.name.monkey.retromusic.util.RetroColorUtil.getColor import code.name.monkey.retromusic.util.RetroColorUtil.getColor
@ -31,6 +30,7 @@ import org.jaudiotagger.tag.FieldKey
import java.util.* import java.util.*
class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher { class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override val contentViewLayout: Int override val contentViewLayout: Int
get() = R.layout.activity_album_tag_editor get() = R.layout.activity_album_tag_editor
@ -162,13 +162,13 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
writeValuesToFiles( writeValuesToFiles(
fieldKeyValueMap, fieldKeyValueMap,
if (deleteAlbumArt) AbsTagEditorActivity.ArtworkInfo(id, null) if (deleteAlbumArt) ArtworkInfo(id, null)
else if (albumArtBitmap == null) null else ArtworkInfo(id, albumArtBitmap!!) else if (albumArtBitmap == null) null else ArtworkInfo(id, albumArtBitmap!!)
) )
} }
override fun getSongPaths(): List<String> { override suspend fun getSongPaths(): List<String> {
val songs = AlbumLoader.getAlbum(this, id).songs val songs = repository.albumById(id).songs
val paths = ArrayList<String>(songs!!.size) val paths = ArrayList<String>(songs!!.size)
for (song in songs) { for (song in songs) {
paths.add(song.data) 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.appthemehelper.util.MaterialUtil
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.appHandleColor 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 kotlinx.android.synthetic.main.activity_song_tag_editor.*
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject
import java.util.* import java.util.*
class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher { class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
@ -18,6 +19,8 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
override val contentViewLayout: Int override val contentViewLayout: Int
get() = R.layout.activity_song_tag_editor get() = R.layout.activity_song_tag_editor
private val songRepository by inject<SongRepository>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -85,9 +88,9 @@ class SongTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
writeValuesToFiles(fieldKeyValueMap, null) writeValuesToFiles(fieldKeyValueMap, null)
} }
override fun getSongPaths(): List<String> { override suspend fun getSongPaths(): List<String> {
val paths = ArrayList<String>(1) val paths = ArrayList<String>(1)
paths.add(SongLoader.getSong(this, id).data) paths.add(songRepository.song(id).data)
return paths return paths
} }

View File

@ -63,7 +63,7 @@ public class WriteTagsAsyncTask extends DialogAsyncTask<WriteTagsAsyncTask.Loadi
File albumArtFile = null; File albumArtFile = null;
if (info.artworkInfo != null && info.artworkInfo.getArtwork() != null) { if (info.artworkInfo != null && info.artworkInfo.getArtwork() != null) {
try { try {
albumArtFile = MusicUtil.createAlbumArtFile().getCanonicalFile(); albumArtFile = MusicUtil.INSTANCE.createAlbumArtFile().getCanonicalFile();
info.artworkInfo.getArtwork() info.artworkInfo.getArtwork()
.compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile)); .compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile));
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile); artwork = ArtworkFactory.createArtworkFromFile(albumArtFile);
@ -120,9 +120,9 @@ public class WriteTagsAsyncTask extends DialogAsyncTask<WriteTagsAsyncTask.Loadi
Context context = getContext(); Context context = getContext();
if (context != null) { if (context != null) {
if (wroteArtwork) { if (wroteArtwork) {
MusicUtil.insertAlbumArt(context, info.artworkInfo.getAlbumId(), albumArtFile.getPath()); MusicUtil.INSTANCE.insertAlbumArt(context, info.artworkInfo.getAlbumId(), albumArtFile.getPath());
} else if (deletedArtwork) { } 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.glide.ArtistGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.menu.SongMenuHelper 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.*
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist
import code.name.monkey.retromusic.repository.PlaylistSongsLoader
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -93,7 +93,7 @@ class SearchAdapter(
private fun getSongs(playlist: Playlist): java.util.ArrayList<Song> { private fun getSongs(playlist: Playlist): java.util.ArrayList<Song> {
val songs = java.util.ArrayList<Song>() val songs = java.util.ArrayList<Song>()
if (playlist is AbsSmartPlaylist) { if (playlist is AbsSmartPlaylist) {
songs.addAll(playlist.getSongs(activity)) songs.addAll(playlist.getSongs())
} else { } else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id)) 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.adapter.base.MediaEntryViewHolder
import code.name.monkey.retromusic.glide.audiocover.AudioFileCover import code.name.monkey.retromusic.glide.audiocover.AudioFileCover
import code.name.monkey.retromusic.interfaces.CabHolder 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.MusicUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -35,7 +36,6 @@ import com.bumptech.glide.signature.MediaStoreSignature
import me.zhanghai.android.fastscroll.PopupTextProvider import me.zhanghai.android.fastscroll.PopupTextProvider
import java.io.File import java.io.File
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.*
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.pow import kotlin.math.pow
@ -135,9 +135,9 @@ class SongFileAdapter(
return getFileTitle(`object`) return getFileTitle(`object`)
} }
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<File>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: List<File>) {
if (callbacks == null) return if (callbacks == null) return
callbacks.onMultipleItemAction(menuItem, selection) callbacks.onMultipleItemAction(menuItem, selection as ArrayList<File>)
} }
override fun getPopupText(position: Int): String { override fun getPopupText(position: Int): String {
@ -148,13 +148,6 @@ class SongFileAdapter(
return MusicUtil.getSectionName(dataSet[position].name) 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) { inner class ViewHolder(itemView: View) : MediaEntryViewHolder(itemView) {

View File

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

View File

@ -107,12 +107,12 @@ class ArtistAdapter(
} }
override fun onMultipleItemAction( override fun onMultipleItemAction(
menuItem: MenuItem, selection: ArrayList<Artist> menuItem: MenuItem, selection: List<Artist>
) { ) {
SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.itemId) 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>() val songs = ArrayList<Song>()
for (artist in artists) { for (artist in artists) {
songs.addAll(artist.songs) // maybe async in future? 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 com.afollestad.materialcab.MaterialCab;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.interfaces.CabHolder; 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 CabHolder cabHolder;
private final Context context; private final Context context;
private MaterialCab cab; private MaterialCab cab;
private ArrayList<I> checked; private List<I> checked;
private int menuRes; private int menuRes;
public AbsMultiSelectAdapter(@NonNull Context context, @Nullable CabHolder cabHolder, @MenuRes 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(); 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) { protected void setMultiSelectMenuRes(@MenuRes int menuRes) {
this.menuRes = 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.PlaylistMenuHelper
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper import code.name.monkey.retromusic.helper.menu.SongsMenuHelper
import code.name.monkey.retromusic.interfaces.CabHolder 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.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.model.smartplaylist.AbsSmartPlaylist 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.AutoGeneratedPlaylistBitmap
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroColorUtil import code.name.monkey.retromusic.util.RetroColorUtil
@ -122,7 +122,7 @@ class PlaylistAdapter(
return playlist.name return playlist.name
} }
override fun onMultipleItemAction(menuItem: MenuItem, selection: ArrayList<Playlist>) { override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Playlist>) {
when (menuItem.itemId) { when (menuItem.itemId) {
else -> SongsMenuHelper.handleMenuClick( else -> SongsMenuHelper.handleMenuClick(
activity, 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>() val songs = ArrayList<Song>()
for (playlist in playlists) { for (playlist in playlists) {
if (playlist is AbsCustomPlaylist) { if (playlist is AbsCustomPlaylist) {
songs.addAll(playlist.getSongs(activity)) songs.addAll(playlist.songs())
} else { } else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id)) songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id))
} }
@ -144,12 +144,12 @@ class PlaylistAdapter(
return songs return songs
} }
private fun getSongs(playlist: Playlist): ArrayList<Song> { private fun getSongs(playlist: Playlist): List<Song> {
val songs = ArrayList<Song>() val songs = ArrayList<Song>()
if (playlist is AbsSmartPlaylist) { if (playlist is AbsSmartPlaylist) {
songs.addAll(playlist.getSongs(activity)) songs.addAll(playlist.songs())
} else { } else {
songs.addAll(PlaylistSongsLoader.getPlaylistSongList(activity, playlist.id)) songs.addAll(playlist.getSongs())
} }
return songs return songs
} }

View File

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

View File

@ -133,7 +133,7 @@ open class SongAdapter(
return song.title 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) 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.appshortcuts.shortcuttype.TopTracksShortcutType
import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist 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.ShuffleAllPlaylist
import code.name.monkey.retromusic.model.smartplaylist.TopTracksPlaylist
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
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 -> { SHORTCUT_TYPE_TOP_TRACKS -> {
startServiceWithPlaylist( startServiceWithPlaylist(
SHUFFLE_MODE_NONE, MyTopTracksPlaylist(applicationContext) SHUFFLE_MODE_NONE, TopTracksPlaylist(applicationContext)
) )
DynamicShortcutManager.reportShortcutUsed(this, TopTracksShortcutType.id) DynamicShortcutManager.reportShortcutUsed(this, TopTracksShortcutType.id)
} }

View File

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

View File

@ -98,7 +98,9 @@ class DeleteSongsDialog : DialogFragment() {
} }
fun deleteSongs(songs: List<Song>, safUris: List<Uri>?) { fun deleteSongs(songs: List<Song>, safUris: List<Uri>?) {
MusicUtil.deleteTracks(requireActivity(), songs, safUris) { this.dismiss() } MusicUtil.deleteTracks(requireActivity(), songs, safUris, Runnable {
dismiss()
})
} }
companion object { 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.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.providers.RepositoryImpl import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.android.synthetic.main.fragment_playlist_detail.* import kotlinx.android.synthetic.main.fragment_playlist_detail.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -28,7 +28,7 @@ import org.koin.android.ext.android.inject
class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail), class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_detail),
ArtistClickListener, AlbumClickListener { ArtistClickListener, AlbumClickListener {
private val args by navArgs<DetailListFragmentArgs>() private val args by navArgs<DetailListFragmentArgs>()
private val repository by inject<RepositoryImpl>() private val repository by inject<RealRepository>()
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) 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.fragments.ReloadType.*
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.* 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.Deferred
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class LibraryViewModel( class LibraryViewModel(
private val repository: RepositoryImpl private val realRepository: RealRepository
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val paletteColor = MutableLiveData<Int>() private val paletteColor = MutableLiveData<Int>()
@ -49,29 +49,29 @@ class LibraryViewModel(
} }
private val loadHome: Deferred<List<Home>> private val loadHome: Deferred<List<Home>>
get() = viewModelScope.async { repository.homeSections() } get() = viewModelScope.async { realRepository.homeSections() }
private val loadSongs: Deferred<List<Song>> private val loadSongs: Deferred<List<Song>>
get() = viewModelScope.async(IO) { repository.allSongs() } get() = viewModelScope.async(IO) { realRepository.allSongs() }
private val loadAlbums: Deferred<List<Album>> private val loadAlbums: Deferred<List<Album>>
get() = viewModelScope.async(IO) { get() = viewModelScope.async(IO) {
repository.allAlbums() realRepository.allAlbums()
} }
private val loadArtists: Deferred<List<Artist>> private val loadArtists: Deferred<List<Artist>>
get() = viewModelScope.async(IO) { get() = viewModelScope.async(IO) {
repository.allArtists() realRepository.allArtists()
} }
private val loadPlaylists: Deferred<List<Playlist>> private val loadPlaylists: Deferred<List<Playlist>>
get() = viewModelScope.async(IO) { get() = viewModelScope.async(IO) {
repository.allPlaylists() realRepository.allPlaylists()
} }
private val loadGenres: Deferred<List<Genre>> private val loadGenres: Deferred<List<Genre>>
get() = viewModelScope.async(IO) { 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.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.model.LastFmAlbum 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.Deferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class AlbumDetailsViewModel( class AlbumDetailsViewModel(
private val repository: RepositoryImpl, private val realRepository: RealRepository,
private val albumId: Int private val albumId: Int
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
@ -39,12 +39,12 @@ class AlbumDetailsViewModel(
} }
fun loadAlbumInfo(album: Album) = viewModelScope.launch(Dispatchers.IO) { 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) _lastFmAlbum.postValue(lastFmAlbum)
} }
fun loadArtist(artistId: Int) = viewModelScope.launch(Dispatchers.IO) { fun loadArtist(artistId: Int) = viewModelScope.launch(Dispatchers.IO) {
val artist = repository.artistById(artistId) val artist = realRepository.artistById(artistId)
_artist.postValue(artist) _artist.postValue(artist)
artist.albums?.filter { item -> item.id != albumId }?.let { albums -> artist.albums?.filter { item -> item.id != albumId }?.let { albums ->
@ -54,7 +54,7 @@ class AlbumDetailsViewModel(
private val loadAlbumAsync: Deferred<Album?> private val loadAlbumAsync: Deferred<Album?>
get() = viewModelScope.async(Dispatchers.IO) { get() = viewModelScope.async(Dispatchers.IO) {
repository.albumById(albumId) realRepository.albumById(albumId)
} }
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {

View File

@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Artist 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 code.name.monkey.retromusic.network.model.LastFmArtist
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -14,13 +14,13 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ArtistDetailsViewModel( class ArtistDetailsViewModel(
private val repository: RepositoryImpl, private val realRepository: RealRepository,
private val artistId: Int private val artistId: Int
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val loadArtistDetailsAsync: Deferred<Artist?> private val loadArtistDetailsAsync: Deferred<Artist?>
get() = viewModelScope.async(Dispatchers.IO) { get() = viewModelScope.async(Dispatchers.IO) {
repository.artistById(artistId) realRepository.artistById(artistId)
} }
private val _artist = MutableLiveData<Artist>() private val _artist = MutableLiveData<Artist>()
@ -40,7 +40,7 @@ class ArtistDetailsViewModel(
} }
fun loadBiography(name: String, lang: String?, cache: String?) = viewModelScope.launch { 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) _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.SongMenuHelper;
import code.name.monkey.retromusic.helper.menu.SongsMenuHelper; import code.name.monkey.retromusic.helper.menu.SongsMenuHelper;
import code.name.monkey.retromusic.interfaces.CabHolder; 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.interfaces.MainActivityFragmentCallbacks;
import code.name.monkey.retromusic.misc.DialogAsyncTask; import code.name.monkey.retromusic.misc.DialogAsyncTask;
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener; import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
@ -76,7 +77,9 @@ import me.zhanghai.android.fastscroll.FastScroller;
public class FoldersFragment extends AbsMainActivityFragment implements public class FoldersFragment extends AbsMainActivityFragment implements
MainActivityFragmentCallbacks, MainActivityFragmentCallbacks,
CabHolder, BreadCrumbLayout.SelectionCallback, SongFileAdapter.Callbacks, CabHolder,
BreadCrumbLayout.SelectionCallback,
Callbacks,
LoaderManager.LoaderCallbacks<List<File>> { LoaderManager.LoaderCallbacks<List<File>> {
public static final String TAG = FoldersFragment.class.getSimpleName(); public static final String TAG = FoldersFragment.class.getSimpleName();
@ -619,7 +622,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
} }
private static class ListSongsAsyncTask private static class ListSongsAsyncTask
extends ListingFilesDialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, ArrayList<Song>> { extends ListingFilesDialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, List<Song>> {
private final Object extra; private final Object extra;
private WeakReference<OnSongsListedCallback> callbackWeakReference; private WeakReference<OnSongsListedCallback> callbackWeakReference;
@ -633,7 +636,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
} }
@Override @Override
protected ArrayList<Song> doInBackground(LoadingInfo... params) { protected List<Song> doInBackground(LoadingInfo... params) {
try { try {
LoadingInfo info = params[0]; LoadingInfo info = params[0];
List<File> files = FileUtil.listFilesDeep(info.files, info.fileFilter); List<File> files = FileUtil.listFilesDeep(info.files, info.fileFilter);
@ -659,7 +662,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
} }
@Override @Override
protected void onPostExecute(ArrayList<Song> songs) { protected void onPostExecute(List<Song> songs) {
super.onPostExecute(songs); super.onPostExecute(songs);
OnSongsListedCallback callback = checkCallbackReference(); OnSongsListedCallback callback = checkCallbackReference();
if (songs != null && callback != null) { if (songs != null && callback != null) {
@ -692,7 +695,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
public interface OnSongsListedCallback { public interface OnSongsListedCallback {
void onSongsListed(@NonNull ArrayList<Song> songs, Object extra); void onSongsListed(@NonNull List<Song> songs, Object extra);
} }
static class LoadingInfo { 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.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.model.Song 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.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class GenreDetailsViewModel( class GenreDetailsViewModel(
private val repository: RepositoryImpl, private val realRepository: RealRepository,
private val genre: Genre private val genre: Genre
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
@ -31,7 +31,7 @@ class GenreDetailsViewModel(
} }
private fun loadGenreSongs(genre: Genre) = viewModelScope.launch { private fun loadGenreSongs(genre: Genre) = viewModelScope.launch {
val songs = repository.getGenre(genre.id) val songs = realRepository.getGenre(genre.id)
withContext(Main) { _playListSongs.postValue(songs) } withContext(Main) { _playListSongs.postValue(songs) }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -34,7 +34,7 @@ public class AlbumGlideRequest {
if (ignoreMediaStore) { if (ignoreMediaStore) {
return requestManager.load(new AudioFileCover(song.getData())); return requestManager.load(new AudioFileCover(song.getData()));
} else { } 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) { if (ignoreMediaStore) {
return requestManager.load(new AudioFileCover(song.getData())); return requestManager.load(new AudioFileCover(song.getData()));
} else { } 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? { ): File? {
if (!dir.exists()) dir.mkdirs() if (!dir.exists()) dir.mkdirs()
val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION) val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION)
val songs = playlist.getSongs(context) val songs = playlist.getSongs()
if (songs.size > 0) { if (songs.size > 0) {
val bw = BufferedWriter(FileWriter(file)) val bw = BufferedWriter(FileWriter(file))
bw.write(M3UConstants.HEADER) bw.write(M3UConstants.HEADER)

View File

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

View File

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

View File

@ -14,42 +14,43 @@
package code.name.monkey.retromusic.helper.menu package code.name.monkey.retromusic.helper.menu
import android.app.Activity
import android.view.MenuItem import android.view.MenuItem
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.helper.MusicPlayerRemote 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.Genre
import code.name.monkey.retromusic.model.Song 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 { fun handleMenuClick(activity: FragmentActivity, genre: Genre, item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_play -> { R.id.action_play -> {
MusicPlayerRemote.openQueue(getGenreSongs(activity, genre), 0, true) MusicPlayerRemote.openQueue(getGenreSongs(genre), 0, true)
return true return true
} }
R.id.action_play_next -> { R.id.action_play_next -> {
MusicPlayerRemote.playNext(getGenreSongs(activity, genre)) MusicPlayerRemote.playNext(getGenreSongs(genre))
return true return true
} }
R.id.action_add_to_playlist -> { R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(getGenreSongs(activity, genre)) AddToPlaylistDialog.create(getGenreSongs(genre))
.show(activity.supportFragmentManager, "ADD_PLAYLIST") .show(activity.supportFragmentManager, "ADD_PLAYLIST")
return true return true
} }
R.id.action_add_to_current_playing -> { R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(getGenreSongs(activity, genre)) MusicPlayerRemote.enqueue(getGenreSongs(genre))
return true return true
} }
} }
return false return false
} }
private fun getGenreSongs(activity: Activity, genre: Genre): ArrayList<Song> { private fun getGenreSongs(genre: Genre): List<Song> {
return GenreLoader.getSongs(activity, genre.id) 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.DeletePlaylistDialog
import code.name.monkey.retromusic.dialogs.RenamePlaylistDialog import code.name.monkey.retromusic.dialogs.RenamePlaylistDialog
import code.name.monkey.retromusic.helper.MusicPlayerRemote 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.misc.WeakContextAsyncTask
import code.name.monkey.retromusic.model.AbsCustomPlaylist import code.name.monkey.retromusic.model.AbsCustomPlaylist
import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.PlaylistsUtil import code.name.monkey.retromusic.util.PlaylistsUtil
import java.util.*
object PlaylistMenuHelper { object PlaylistMenuHelper {
@ -80,11 +78,11 @@ object PlaylistMenuHelper {
private fun getPlaylistSongs( private fun getPlaylistSongs(
activity: Activity, activity: Activity,
playlist: Playlist playlist: Playlist
): ArrayList<Song> { ): List<Song> {
return if (playlist is AbsCustomPlaylist) { return if (playlist is AbsCustomPlaylist) {
playlist.getSongs(activity) playlist.songs()
} else { } else {
PlaylistSongsLoader.getPlaylistSongList(activity, playlist) playlist.getSongs()
} }
} }

View File

@ -15,19 +15,17 @@
package code.name.monkey.retromusic.helper.menu package code.name.monkey.retromusic.helper.menu
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog import code.name.monkey.retromusic.dialogs.AddToPlaylistDialog
import code.name.monkey.retromusic.dialogs.DeleteSongsDialog import code.name.monkey.retromusic.dialogs.DeleteSongsDialog
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import java.util.*
object SongsMenuHelper { object SongsMenuHelper {
fun handleMenuClick( fun handleMenuClick(
activity: FragmentActivity, activity: FragmentActivity,
songs: ArrayList<Song>, songs: List<Song>,
menuItemId: Int menuItemId: Int
): Boolean { ): Boolean {
when (menuItemId) { 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.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List; 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.model.Song;
import code.name.monkey.retromusic.repository.RealSongRepository;
/** /**
* @author Andrew Neal, modified for Phonograph by Karim Abou Zeid * @author Andrew Neal, modified for Phonograph by Karim Abou Zeid
@ -76,12 +76,12 @@ public class MusicPlaybackQueueStore extends SQLiteOpenHelper {
} }
@NonNull @NonNull
public ArrayList<Song> getSavedOriginalPlayingQueue() { public List<Song> getSavedOriginalPlayingQueue() {
return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME); return getQueue(ORIGINAL_PLAYING_QUEUE_TABLE_NAME);
} }
@NonNull @NonNull
public ArrayList<Song> getSavedPlayingQueue() { public List<Song> getSavedPlayingQueue() {
return getQueue(PLAYING_QUEUE_TABLE_NAME); return getQueue(PLAYING_QUEUE_TABLE_NAME);
} }
@ -157,10 +157,10 @@ public class MusicPlaybackQueueStore extends SQLiteOpenHelper {
} }
@NonNull @NonNull
private ArrayList<Song> getQueue(@NonNull final String tableName) { private List<Song> getQueue(@NonNull final String tableName) {
Cursor cursor = getReadableDatabase().query(tableName, null, Cursor cursor = getReadableDatabase().query(tableName, null,
null, null, null, null, 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. * 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 android.provider.MediaStore.Audio.AudioColumns
import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
@ -27,16 +26,31 @@ import kotlin.collections.ArrayList
/** /**
* Created by hemanths on 11/08/17. * Created by hemanths on 11/08/17.
*/ */
interface AlbumRepository {
fun albums(): List<Album>
object AlbumLoader { fun albums(query: String): List<Album>
fun getAlbums( fun album(albumId: Int): Album
context: Context, }
query: String
): ArrayList<Album> { class RealAlbumRepository(private val songRepository: RealSongRepository) :
val songs = SongLoader.getSongs( AlbumRepository {
SongLoader.makeSongCursor(
context, 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 ?", AudioColumns.ALBUM + " LIKE ?",
arrayOf("%$query%"), arrayOf("%$query%"),
getSongLoaderSortOrder() getSongLoaderSortOrder()
@ -45,41 +59,22 @@ object AlbumLoader {
return splitIntoAlbums(songs) return splitIntoAlbums(songs)
} }
@JvmStatic override fun album(albumId: Int): Album {
fun getAlbum( val songs = songRepository.songs(
context: Context, songRepository.makeSongCursor(
albumId: Int
): Album {
val songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
AudioColumns.ALBUM_ID + "=?", AudioColumns.ALBUM_ID + "=?",
arrayOf(albumId.toString()), arrayOf(albumId.toString()),
getSongLoaderSortOrder() getSongLoaderSortOrder()
) )
) )
val album = Album(songs) val album = Album(ArrayList(songs))
sortSongsByTrackNumber(album) sortAlbumSongs(album)
return album return album
} }
fun getAllAlbums(
context: Context
): ArrayList<Album> {
val songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
null,
null,
getSongLoaderSortOrder()
)
)
return splitIntoAlbums(songs)
}
fun splitIntoAlbums( fun splitIntoAlbums(
songs: ArrayList<Song>? songs: List<Song>?
): ArrayList<Album> { ): List<Album> {
val albums = ArrayList<Album>() val albums = ArrayList<Album>()
if (songs != null) { if (songs != null) {
for (song in songs) { for (song in songs) {
@ -87,7 +82,7 @@ object AlbumLoader {
} }
} }
for (album in albums) { for (album in albums) {
sortSongsByTrackNumber(album) sortAlbumSongs(album)
} }
return albums return albums
} }
@ -106,7 +101,7 @@ object AlbumLoader {
return album return album
} }
private fun sortSongsByTrackNumber(album: Album) { private fun sortAlbumSongs(album: Album) {
when (PreferenceUtil.albumDetailSongSortOrder) { when (PreferenceUtil.albumDetailSongSortOrder) {
SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs?.sortWith(Comparator { o1, o2 -> SortOrder.AlbumSongSortOrder.SONG_TRACK_LIST -> album.songs?.sortWith(Comparator { o1, o2 ->
o1.trackNumber.compareTo( o1.trackNumber.compareTo(
@ -135,4 +130,6 @@ object AlbumLoader {
return PreferenceUtil.albumSortOrder + ", " + return PreferenceUtil.albumSortOrder + ", " +
PreferenceUtil.albumSongSortOrder PreferenceUtil.albumSongSortOrder
} }
} }

View File

@ -12,46 +12,66 @@
* See the GNU General Public License for more details. * 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 android.provider.MediaStore.Audio.AudioColumns
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
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 { private fun getSongLoaderSortOrder(): String {
return PreferenceUtil.artistSortOrder + ", " + return PreferenceUtil.artistSortOrder + ", " +
PreferenceUtil.artistAlbumSortOrder + ", " + PreferenceUtil.artistAlbumSortOrder + ", " +
PreferenceUtil.artistSongSortOrder PreferenceUtil.artistSongSortOrder
} }
fun getAllArtists(context: Context): ArrayList<Artist> { override fun artists(): List<Artist> {
val songs = SongLoader.getSongs( val songs = songRepository.songs(
SongLoader.makeSongCursor( songRepository.makeSongCursor(
context,
null, null, null, null,
getSongLoaderSortOrder() getSongLoaderSortOrder()
) )
) )
return splitIntoArtists(AlbumLoader.splitIntoAlbums(songs)) return splitIntoArtists(albumRepository.splitIntoAlbums(songs))
} }
fun getArtists(context: Context, query: String): ArrayList<Artist> { override fun artists(query: String): List<Artist> {
val songs = SongLoader.getSongs( val songs = songRepository.songs(
SongLoader.makeSongCursor( songRepository.makeSongCursor(
context,
AudioColumns.ARTIST + " LIKE ?", AudioColumns.ARTIST + " LIKE ?",
arrayOf("%$query%"), arrayOf("%$query%"),
getSongLoaderSortOrder() getSongLoaderSortOrder()
) )
) )
return splitIntoArtists(AlbumLoader.splitIntoAlbums(songs)) return splitIntoArtists(albumRepository.splitIntoAlbums(songs))
} }
fun splitIntoArtists(albums: ArrayList<Album>?): ArrayList<Artist> { override fun artist(artistId: Int): Artist {
val artists = ArrayList<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) { if (albums != null) {
for (album in albums) { for (album in albums) {
getOrCreateArtist(artists, album.artistId).albums!!.add(album) getOrCreateArtist(artists, album.artistId).albums!!.add(album)
@ -60,7 +80,7 @@ object ArtistLoader {
return artists return artists
} }
private fun getOrCreateArtist(artists: ArrayList<Artist>, artistId: Int): Artist { private fun getOrCreateArtist(artists: MutableList<Artist>, artistId: Int): Artist {
for (artist in artists) { for (artist in artists) {
if (artist.albums!!.isNotEmpty() && artist.albums[0].songs!!.isNotEmpty() && artist.albums[0].songs!![0].artistId == artistId) { if (artist.albums!!.isNotEmpty() && artist.albums[0].songs!!.isNotEmpty() && artist.albums[0].songs!![0].artistId == artistId) {
return artist return artist
@ -70,17 +90,4 @@ object ArtistLoader {
artists.add(album) artists.add(album)
return 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. * 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.database.Cursor
import android.net.Uri import android.net.Uri
import android.provider.BaseColumns import android.provider.BaseColumns
import android.provider.MediaStore
import android.provider.MediaStore.Audio.Genres import android.provider.MediaStore.Audio.Genres
import code.name.monkey.retromusic.Constants.IS_MUSIC import code.name.monkey.retromusic.Constants.IS_MUSIC
import code.name.monkey.retromusic.Constants.baseProjection 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.model.Song
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
interface GenreRepository {
fun genres(): List<Genre>
object GenreLoader { fun songs(genreId: Int): List<Song>
}
fun getAllGenres(context: Context): ArrayList<Genre> { class RealGenreRepository(
return getGenresFromCursor(context, makeGenreCursor(context)) private val contentResolver: ContentResolver,
private val songRepository: RealSongRepository
) : GenreRepository {
override fun genres(): List<Genre> {
return getGenresFromCursor(makeGenreCursor())
} }
fun searchGenres(context: Context): ArrayList<Genre> { override fun songs(genreId: Int): List<Song> {
return getGenresFromCursorForSearch(context, makeGenreCursor(context))
}
fun getSongs(context: Context, genreId: Int): ArrayList<Song> {
// The genres table only stores songs that have a genre specified, // The genres table only stores songs that have a genre specified,
// so we need to get songs without a genre a different way. // so we need to get songs without a genre a different way.
return if (genreId == -1) { return if (genreId == -1) {
getSongsWithNoGenre(context) getSongsWithNoGenre()
} else SongLoader.getSongs(makeGenreSongCursor(context, genreId)) } 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 id = cursor.getInt(0)
val name = cursor.getString(1) val name = cursor.getString(1)
val songCount = getSongs(context, id).size val songCount = songs(id).size
return Genre(id, name, songCount) 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 id = cursor.getInt(0)
val name = cursor.getString(1) val name = cursor.getString(1)
return Genre(id, name, -1) return Genre(id, name, -1)
} }
private fun getSongsWithNoGenre(context: Context): ArrayList<Song> { private fun getSongsWithNoGenre(): List<Song> {
val selection = BaseColumns._ID + " NOT IN " + val selection =
"(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)" BaseColumns._ID + " NOT IN " + "(SELECT " + Genres.Members.AUDIO_ID + " FROM audio_genres_map)"
return SongLoader.getSongs(SongLoader.makeSongCursor(context, selection, null)) return songRepository.songs(songRepository.makeSongCursor(selection, null))
} }
private fun hasSongsWithNoGenre(context: Context): Boolean { private fun hasSongsWithNoGenre(): Boolean {
val allSongsCursor = SongLoader.makeSongCursor(context, null, null) val allSongsCursor = songRepository.makeSongCursor(null, null)
val allSongsWithGenreCursor = makeAllSongsWithGenreCursor(context) val allSongsWithGenreCursor = makeAllSongsWithGenreCursor()
if (allSongsCursor == null || allSongsWithGenreCursor == null) { if (allSongsCursor == null || allSongsWithGenreCursor == null) {
return false return false
@ -79,44 +83,36 @@ object GenreLoader {
return hasSongsWithNoGenre return hasSongsWithNoGenre
} }
private fun makeAllSongsWithGenreCursor(context: Context): Cursor? { private fun makeAllSongsWithGenreCursor(): Cursor? {
return try { println(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI.toString())
context.contentResolver.query( return contentResolver.query(
Uri.parse("content://media/external/audio/genres/all/members"), Uri.parse("content://media/external/audio/genres/all/members"),
arrayOf(Genres.Members.AUDIO_ID), null, null, null arrayOf(Genres.Members.AUDIO_ID), null, null, null
) )
} catch (e: SecurityException) {
null
}
} }
private fun makeGenreSongCursor(context: Context, genreId: Int): Cursor? { private fun makeGenreSongCursor(genreId: Int): Cursor? {
try { return contentResolver.query(
return context.contentResolver.query(
Genres.Members.getContentUri("external", genreId.toLong()), Genres.Members.getContentUri("external", genreId.toLong()),
baseProjection, baseProjection,
IS_MUSIC, IS_MUSIC,
null, null,
PreferenceUtil.songSortOrder PreferenceUtil.songSortOrder
) )
} catch (e: SecurityException) {
return null
} }
} private fun getGenresFromCursor(cursor: Cursor?): ArrayList<Genre> {
private fun getGenresFromCursor(context: Context, cursor: Cursor?): ArrayList<Genre> {
val genres = arrayListOf<Genre>() val genres = arrayListOf<Genre>()
if (cursor != null) { if (cursor != null) {
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
val genre = getGenreFromCursor(context, cursor) val genre = getGenreFromCursor(cursor)
if (genre.songCount > 0) { if (genre.songCount > 0) {
genres.add(genre) genres.add(genre)
} else { } else {
// try to remove the empty genre from the media store // try to remove the empty genre from the media store
try { try {
context.contentResolver.delete( contentResolver.delete(
Genres.EXTERNAL_CONTENT_URI, Genres.EXTERNAL_CONTENT_URI,
Genres._ID + " == " + genre.id, Genres._ID + " == " + genre.id,
null null
@ -133,11 +129,11 @@ object GenreLoader {
return genres return genres
} }
private fun getGenresFromCursorForSearch(context: Context, cursor: Cursor?): ArrayList<Genre> { private fun getGenresFromCursorForSearch(cursor: Cursor?): List<Genre> {
val genres = arrayListOf<Genre>() val genres = mutableListOf<Genre>()
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
do { do {
genres.add(getGenreFromCursorWithOutSongs(context, cursor)) genres.add(getGenreFromCursorWithOutSongs(cursor))
} while (cursor.moveToNext()) } while (cursor.moveToNext())
} }
cursor?.close() 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) val projection = arrayOf(Genres._ID, Genres.NAME)
try { return contentResolver.query(
return context.contentResolver.query(
Genres.EXTERNAL_CONTENT_URI, Genres.EXTERNAL_CONTENT_URI,
projection, projection,
null, null,
null, null,
PreferenceUtil.genreSortOrder PreferenceUtil.genreSortOrder
) )
} catch (e: SecurityException) {
return null
}
} }
} }

View File

@ -12,9 +12,8 @@
* See the GNU General Public License for more details. * 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.database.Cursor
import android.provider.MediaStore import android.provider.MediaStore
import code.name.monkey.retromusic.model.Album 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. * 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> { fun recentArtists(): List<Artist>
return SongLoader.getSongs(makeLastAddedCursor(context)) }
class RealLastAddedRepository(
private val songRepository: RealSongRepository,
private val albumRepository: RealAlbumRepository,
private val artistRepository: RealArtistRepository
) : LastAddedRepository {
override fun recentSongs(): List<Song> {
return songRepository.songs(makeLastAddedCursor())
} }
private fun makeLastAddedCursor(context: Context): Cursor? { 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 val cutoff = PreferenceUtil.lastAddedCutoff
return SongLoader.makeSongCursor( return songRepository.makeSongCursor(
context,
MediaStore.Audio.Media.DATE_ADDED + ">?", MediaStore.Audio.Media.DATE_ADDED + ">?",
arrayOf(cutoff.toString()), arrayOf(cutoff.toString()),
MediaStore.Audio.Media.DATE_ADDED + " DESC" 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. * 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.Context
import android.database.Cursor import android.database.Cursor
@ -34,19 +34,31 @@ object PlaylistSongsLoader {
fun getPlaylistSongList( fun getPlaylistSongList(
context: Context, context: Context,
playlist: Playlist playlist: Playlist
): ArrayList<Song> { ): List<Song> {
return (playlist as? AbsCustomPlaylist)?.getSongs(context) return if (playlist is AbsCustomPlaylist) {
?: getPlaylistSongList(context, playlist.id) return playlist.songs()
} else {
getPlaylistSongList(context, playlist.id)
}
} }
@JvmStatic @JvmStatic
fun getPlaylistSongList(context: Context, playlistId: Int): ArrayList<Song> { fun getPlaylistSongList(context: Context, playlistId: Int): ArrayList<Song> {
val songs = arrayListOf<Song>() val songs = arrayListOf<Song>()
val cursor = makePlaylistSongCursor(context, playlistId) val cursor =
makePlaylistSongCursor(
context,
playlistId
)
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
do { do {
songs.add(getPlaylistSongFromCursorImpl(cursor, playlistId)) songs.add(
getPlaylistSongFromCursorImpl(
cursor,
playlistId
)
)
} while (cursor.moveToNext()) } while (cursor.moveToNext())
} }
cursor?.close() cursor?.close()

View File

@ -12,68 +12,133 @@
* See the GNU General Public License for more details. * 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 android.content.Context
import code.name.monkey.retromusic.* import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.loaders.*
import code.name.monkey.retromusic.model.* 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.LastFMService
import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.network.model.LastFmAlbum
import code.name.monkey.retromusic.network.model.LastFmArtist import code.name.monkey.retromusic.network.model.LastFmArtist
import code.name.monkey.retromusic.providers.interfaces.Repository
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flow 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 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 { ) : 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 = override suspend fun artistById(artistId: Int): Artist = artistRepository.artist(artistId)
ArtistLoader.getArtist(context, artistId)
override suspend fun recentArtists(): List<Artist> = override suspend fun recentArtists(): List<Artist> = lastAddedRepository.recentArtists()
LastAddedSongsLoader.getLastAddedArtists(context)
override suspend fun topArtists(): List<Artist> = override suspend fun recentAlbums(): List<Album> = lastAddedRepository.recentAlbums()
TopAndRecentlyPlayedTracksLoader.getTopArtists(context)
override suspend fun topAlbums(): List<Album> = override suspend fun topArtists(): List<Artist> = playedTracksRepository.topArtists()
TopAndRecentlyPlayedTracksLoader.getTopAlbums(context)
override suspend fun recentAlbums(): List<Album> = override suspend fun topAlbums(): List<Album> = playedTracksRepository.topAlbums()
LastAddedSongsLoader.getLastAddedAlbums(context)
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> = 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) { return if (playlist is AbsCustomPlaylist) {
playlist.getSongs(context) playlist.songs()
} else { } else {
PlaylistSongsLoader.getPlaylistSongList(context, playlist.id) PlaylistSongsLoader.getPlaylistSongList(context, playlist.id)
} }
} }
override suspend fun getGenre(genreId: Int): ArrayList<Song> = override suspend fun getGenre(genreId: Int): List<Song> = genreRepository.songs(genreId)
GenreLoader.getSongs(context, genreId)
override suspend fun artistInfo( override suspend fun artistInfo(
@ -137,13 +202,16 @@ class RepositoryImpl(
} }
suspend fun playlists(): Home { suspend fun playlists(): Home {
val playlist = PlaylistLoader.getAllPlaylists(context) val playlist = playlistRepository.playlists()
return Home(playlist, TOP_ALBUMS) return Home(playlist, TOP_ALBUMS)
} }
suspend fun playlists(playlistId: Int) =
playlistRepository.playlist(playlistId)
override suspend fun suggestionsHome(): Home { override suspend fun suggestionsHome(): Home {
val songs = val songs =
NotRecentlyPlayedPlaylist(context).getSongs(context).shuffled().takeIf { NotPlayedPlaylist(context).songs().shuffled().takeIf {
it.size > 9 it.size > 9
} ?: emptyList() } ?: emptyList()
println(songs.size) println(songs.size)
@ -151,33 +219,34 @@ class RepositoryImpl(
} }
override suspend fun genresHome(): Home { override suspend fun genresHome(): Home {
val genres = GenreLoader.getAllGenres(context).shuffled() val genres = genreRepository.genres().shuffled()
return Home(genres, GENRES) return Home(genres, GENRES)
} }
override suspend fun recentArtistsHome(): Home { override suspend fun recentArtistsHome(): Home {
val artists = LastAddedSongsLoader.getLastAddedArtists(context).take(5) val artists = lastAddedRepository.recentArtists().take(5)
return Home(artists, RECENT_ARTISTS) return Home(artists, RECENT_ARTISTS)
} }
override suspend fun recentAlbumsHome(): Home { override suspend fun recentAlbumsHome(): Home {
val albums = LastAddedSongsLoader.getLastAddedAlbums(context).take(5) val albums = lastAddedRepository.recentAlbums().take(5)
return Home(albums, RECENT_ALBUMS) return Home(albums, RECENT_ALBUMS)
} }
override suspend fun topAlbumsHome(): Home { override suspend fun topAlbumsHome(): Home {
val albums = TopAndRecentlyPlayedTracksLoader.getTopAlbums(context).take(5) val albums = playedTracksRepository.topAlbums().take(5)
return Home(albums, TOP_ALBUMS) return Home(albums, TOP_ALBUMS)
} }
override suspend fun topArtistsHome(): Home { override suspend fun topArtistsHome(): Home {
val artists = TopAndRecentlyPlayedTracksLoader.getTopArtists(context).take(5) val artists = playedTracksRepository.topArtists().take(5)
return Home(artists, TOP_ARTISTS) return Home(artists, TOP_ARTISTS)
} }
override suspend fun favoritePlaylistHome(): Home { 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()) val songs = if (playlists.isNotEmpty())
PlaylistSongsLoader.getPlaylistSongList(context, playlists[0]) PlaylistSongsLoader.getPlaylistSongList(context, playlists[0])
else emptyList<Song>() else emptyList<Song>()
@ -187,7 +256,7 @@ class RepositoryImpl(
override fun songsFlow(): Flow<Result<List<Song>>> = flow { override fun songsFlow(): Flow<Result<List<Song>>> = flow {
emit(Result.Loading) emit(Result.Loading)
val data = SongLoader.getAllSongs(context) val data = songRepository.songs()
if (data.isEmpty()) { if (data.isEmpty()) {
emit(Result.Error) emit(Result.Error)
} else { } else {
@ -197,7 +266,7 @@ class RepositoryImpl(
override fun albumsFlow(): Flow<Result<List<Album>>> = flow { override fun albumsFlow(): Flow<Result<List<Album>>> = flow {
emit(Result.Loading) emit(Result.Loading)
val data = AlbumLoader.getAllAlbums(context) val data = albumRepository.albums()
if (data.isEmpty()) { if (data.isEmpty()) {
emit(Result.Error) emit(Result.Error)
} else { } else {
@ -207,7 +276,7 @@ class RepositoryImpl(
override fun artistsFlow(): Flow<Result<List<Artist>>> = flow { override fun artistsFlow(): Flow<Result<List<Artist>>> = flow {
emit(Result.Loading) emit(Result.Loading)
val data = ArtistLoader.getAllArtists(context) val data = artistRepository.artists()
if (data.isEmpty()) { if (data.isEmpty()) {
emit(Result.Error) emit(Result.Error)
} else { } else {
@ -217,7 +286,7 @@ class RepositoryImpl(
override fun playlistsFlow(): Flow<Result<List<Playlist>>> = flow { override fun playlistsFlow(): Flow<Result<List<Playlist>>> = flow {
emit(Result.Loading) emit(Result.Loading)
val data = PlaylistLoader.getAllPlaylists(context) val data = playlistRepository.playlists()
if (data.isEmpty()) { if (data.isEmpty()) {
emit(Result.Error) emit(Result.Error)
} else { } else {
@ -227,11 +296,14 @@ class RepositoryImpl(
override fun genresFlow(): Flow<Result<List<Genre>>> = flow { override fun genresFlow(): Flow<Result<List<Genre>>> = flow {
emit(Result.Loading) emit(Result.Loading)
val data = GenreLoader.getAllGenres(context) val data = genreRepository.genres()
if (data.isEmpty()) { if (data.isEmpty()) {
emit(Result.Error) emit(Result.Error)
} else { } else {
emit(Result.Success(data)) 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. * 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.Context
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Genre
import java.util.* 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> { fun searchAll(context: Context, query: String?): MutableList<Any> {
val results = mutableListOf<Any>() val results = mutableListOf<Any>()
query?.let { searchString -> query?.let { searchString ->
val songs = SongLoader.getSongs(context, searchString) val songs = songRepository.songs(searchString)
if (songs.isNotEmpty()) { if (songs.isNotEmpty()) {
results.add(context.resources.getString(R.string.songs)) results.add(context.resources.getString(R.string.songs))
results.addAll(songs) results.addAll(songs)
} }
val artists = artistRepository.artists(searchString)
val artists = ArtistLoader.getArtists(context, searchString)
if (artists.isNotEmpty()) { if (artists.isNotEmpty()) {
results.add(context.resources.getString(R.string.artists)) results.add(context.resources.getString(R.string.artists))
results.addAll(artists) results.addAll(artists)
} }
val albums = AlbumLoader.getAlbums(context, searchString) val albums = albumRepository.albums(searchString)
if (albums.isNotEmpty()) { if (albums.isNotEmpty()) {
results.add(context.resources.getString(R.string.albums)) results.add(context.resources.getString(R.string.albums))
results.addAll(albums) results.addAll(albums)
} }
val genres: List<Genre> = GenreLoader.searchGenres(context) val genres: List<Genre> = genreRepository.genres().filter { genre ->
.filter { genre ->
genre.name.toLowerCase(Locale.getDefault()) genre.name.toLowerCase(Locale.getDefault())
.contains(searchString.toLowerCase(Locale.getDefault())) .contains(searchString.toLowerCase(Locale.getDefault()))
} }
@ -49,8 +53,7 @@ object SearchLoader {
results.add(context.resources.getString(R.string.genres)) results.add(context.resources.getString(R.string.genres))
results.addAll(genres) results.addAll(genres)
} }
val playlist = PlaylistLoader.getAllPlaylists(context) val playlist = playlistRepository.playlists().filter { playlist ->
.filter { playlist ->
playlist.name.toLowerCase(Locale.getDefault()) playlist.name.toLowerCase(Locale.getDefault())
.contains(searchString.toLowerCase(Locale.getDefault())) .contains(searchString.toLowerCase(Locale.getDefault()))
} }

View File

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

View File

@ -11,7 +11,7 @@
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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.AbstractCursor;
import android.database.Cursor; import android.database.Cursor;

View File

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

View File

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

View File

@ -31,15 +31,14 @@ import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; 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.model.Song;
import code.name.monkey.retromusic.repository.RealSongRepository;
import code.name.monkey.retromusic.repository.SortedCursor;
public final class FileUtil { public final class FileUtil {
@ -59,9 +58,9 @@ public final class FileUtil {
} }
@NonNull @NonNull
public static ArrayList<Song> matchFilesWithMediaStore(@NonNull Context context, public static List<Song> matchFilesWithMediaStore(@NonNull Context context,
@Nullable List<File> files) { @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) { 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 return songCursor == null ? null
: new SortedCursor(songCursor, paths, MediaStore.Audio.AudioColumns.DATA); : 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(); final StringBuilder selection = new StringBuilder();
selection.append(MediaStore.Audio.Playlists._ID + " IN ("); selection.append(MediaStore.Audio.Playlists._ID + " IN (");
for (int i = 0; i < playlists.size(); i++) { 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) { if (i < playlists.size() - 1) {
selection.append(","); selection.append(",");
} }