My initial commit

Removed Google play dependencies
This commit is contained in:
Muntashir Al-Islam 2020-06-17 22:50:30 +06:00
parent fd582fff69
commit 301ac10570
430 changed files with 2210 additions and 3137 deletions

View file

@ -0,0 +1,50 @@
/*
* 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 io.github.muntashirakon.music.helper
import android.content.Context
import android.view.ViewGroup
import io.github.muntashirakon.music.R
object HorizontalAdapterHelper {
const val LAYOUT_RES = R.layout.item_image
private const val TYPE_FIRST = 1
private const val TYPE_MIDDLE = 2
private const val TYPE_LAST = 3
fun applyMarginToLayoutParams(
context: Context,
layoutParams: ViewGroup.MarginLayoutParams, viewType: Int
) {
val listMargin = context.resources
.getDimensionPixelSize(R.dimen.now_playing_top_margin)
if (viewType == TYPE_FIRST) {
layoutParams.leftMargin = listMargin
} else if (viewType == TYPE_LAST) {
layoutParams.rightMargin = listMargin
}
}
fun getItemViewtype(position: Int, itemCount: Int): Int {
return when (position) {
0 -> TYPE_FIRST
itemCount - 1 -> TYPE_LAST
else -> TYPE_MIDDLE
}
}
}

View file

@ -0,0 +1,22 @@
/*
* 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 io.github.muntashirakon.music.helper;
public interface M3UConstants {
String EXTENSION = "m3u";
String HEADER = "#EXTM3U";
String ENTRY = "#EXTINF:";
String DURATION_SEPARATOR = ",";
}

View file

@ -0,0 +1,47 @@
/*
* 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 io.github.muntashirakon.music.helper
import android.content.Context
import io.github.muntashirakon.music.model.Playlist
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.IOException
object M3UWriter : M3UConstants {
@JvmStatic
@Throws(IOException::class)
fun write(
context: Context,
dir: File,
playlist: Playlist
): File? {
if (!dir.exists()) dir.mkdirs()
val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION)
val songs = playlist.getSongs(context)
if (songs.size > 0) {
val bw = BufferedWriter(FileWriter(file))
bw.write(M3UConstants.HEADER)
for (song in songs) {
bw.newLine()
bw.write(M3UConstants.ENTRY + song.duration + M3UConstants.DURATION_SEPARATOR + song.artistName + " - " + song.title)
bw.newLine()
bw.write(song.data)
}
bw.close()
}
return file
}
}

View file

@ -0,0 +1,484 @@
/*
* 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 io.github.muntashirakon.music.helper
import android.annotation.TargetApi
import android.app.Activity
import android.content.*
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.IBinder
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.widget.Toast
import androidx.core.content.ContextCompat
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.loaders.SongLoader
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.service.MusicService
import io.github.muntashirakon.music.util.PreferenceUtil
import java.io.File
import java.util.*
object MusicPlayerRemote {
val TAG: String = MusicPlayerRemote::class.java.simpleName
private val mConnectionMap = WeakHashMap<Context, ServiceBinder>()
var musicService: MusicService? = null
@JvmStatic
val isPlaying: Boolean
get() = musicService != null && musicService!!.isPlaying
fun isPlaying(song: Song): Boolean {
return if (!isPlaying) {
false
} else song.id == currentSong.id
}
val currentSong: Song
get() = if (musicService != null) {
musicService!!.currentSong
} else Song.emptySong
/**
* Async
*/
var position: Int
get() = if (musicService != null) {
musicService!!.position
} else -1
set(position) {
if (musicService != null) {
musicService!!.position = position
}
}
@JvmStatic
val playingQueue: List<Song>
get() = if (musicService != null) {
musicService?.playingQueue as List<Song>
} else listOf<Song>()
val songProgressMillis: Int
get() = if (musicService != null) {
musicService!!.songProgressMillis
} else -1
val songDurationMillis: Int
get() = if (musicService != null) {
musicService!!.songDurationMillis
} else -1
val repeatMode: Int
get() = if (musicService != null) {
musicService!!.repeatMode
} else MusicService.REPEAT_MODE_NONE
@JvmStatic
val shuffleMode: Int
get() = if (musicService != null) {
musicService!!.shuffleMode
} else MusicService.SHUFFLE_MODE_NONE
val audioSessionId: Int
get() = if (musicService != null) {
musicService!!.audioSessionId
} else -1
val isServiceConnected: Boolean
get() = musicService != null
fun bindToService(context: Context, callback: ServiceConnection): ServiceToken? {
var realActivity: Activity? = (context as Activity).parent
if (realActivity == null) {
realActivity = context
}
val contextWrapper = ContextWrapper(realActivity)
val intent = Intent(contextWrapper, MusicService::class.java)
try {
contextWrapper.startService(intent)
} catch (ignored: IllegalStateException) {
ContextCompat.startForegroundService(context, intent)
}
val binder = ServiceBinder(callback)
if (contextWrapper.bindService(
Intent().setClass(contextWrapper, MusicService::class.java),
binder,
Context.BIND_AUTO_CREATE
)
) {
mConnectionMap[contextWrapper] = binder
return ServiceToken(contextWrapper)
}
return null
}
fun unbindFromService(token: ServiceToken?) {
if (token == null) {
return
}
val mContextWrapper = token.mWrappedContext
val mBinder = mConnectionMap.remove(mContextWrapper) ?: return
mContextWrapper.unbindService(mBinder)
if (mConnectionMap.isEmpty()) {
musicService = null
}
}
private fun getFilePathFromUri(context: Context, uri: Uri): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(column)
try {
cursor = context.contentResolver.query(uri, projection, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
val columnIndex = cursor.getColumnIndexOrThrow(column)
return cursor.getString(columnIndex)
}
} catch (e: Exception) {
println(e.message)
} finally {
cursor?.close()
}
return null
}
fun getQueueDurationSongs(): Int {
return musicService?.playingQueue?.size ?: -1
}
/**
* Async
*/
fun playSongAt(position: Int) {
musicService?.playSongAt(position)
}
fun pauseSong() {
musicService?.pause()
}
/**
* Async
*/
fun playNextSong() {
musicService?.playNextSong(true)
}
/**
* Async
*/
fun playPreviousSong() {
musicService?.playPreviousSong(true)
}
/**
* Async
*/
fun back() {
musicService?.back(true)
}
fun resumePlaying() {
musicService?.play()
}
/**
* Async
*/
@JvmStatic
fun openQueue(queue: List<Song>, startPosition: Int, startPlaying: Boolean) {
if (!tryToHandleOpenPlayingQueue(
queue,
startPosition,
startPlaying
) && musicService != null
) {
musicService?.openQueue(queue, startPosition, startPlaying)
if (PreferenceUtil.isShuffleModeOn)
setShuffleMode(MusicService.SHUFFLE_MODE_NONE)
}
}
/**
* Async
*/
@JvmStatic
fun openAndShuffleQueue(queue: List<Song>, startPlaying: Boolean) {
var startPosition = 0
if (queue.isNotEmpty()) {
startPosition = Random().nextInt(queue.size)
}
if (!tryToHandleOpenPlayingQueue(
queue,
startPosition,
startPlaying
) && musicService != null
) {
openQueue(queue, startPosition, startPlaying)
setShuffleMode(MusicService.SHUFFLE_MODE_SHUFFLE)
}
}
private fun tryToHandleOpenPlayingQueue(
queue: List<Song>,
startPosition: Int,
startPlaying: Boolean
): Boolean {
if (playingQueue === queue) {
if (startPlaying) {
playSongAt(startPosition)
} else {
position = startPosition
}
return true
}
return false
}
fun getQueueDurationMillis(position: Int): Long {
return if (musicService != null) {
musicService!!.getQueueDurationMillis(position)
} else -1
}
fun seekTo(millis: Int): Int {
return if (musicService != null) {
musicService!!.seek(millis)
} else -1
}
fun cycleRepeatMode(): Boolean {
if (musicService != null) {
musicService?.cycleRepeatMode()
return true
}
return false
}
fun toggleShuffleMode(): Boolean {
if (musicService != null) {
musicService?.toggleShuffle()
return true
}
return false
}
fun setShuffleMode(shuffleMode: Int): Boolean {
if (musicService != null) {
musicService!!.shuffleMode = shuffleMode
return true
}
return false
}
fun playNext(song: Song): Boolean {
if (musicService != null) {
if (playingQueue.size > 0) {
musicService?.addSong(position + 1, song)
} else {
val queue = ArrayList<Song>()
queue.add(song)
openQueue(queue, 0, false)
}
Toast.makeText(
musicService,
musicService!!.resources.getString(R.string.added_title_to_playing_queue),
Toast.LENGTH_SHORT
).show()
return true
}
return false
}
fun playNext(songs: List<Song>): Boolean {
if (musicService != null) {
if (playingQueue.size > 0) {
musicService?.addSongs(position + 1, songs)
} else {
openQueue(songs, 0, false)
}
val toast =
if (songs.size == 1) musicService!!.resources.getString(R.string.added_title_to_playing_queue) else musicService!!.resources.getString(
R.string.added_x_titles_to_playing_queue,
songs.size
)
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()
return true
}
return false
}
fun enqueue(song: Song): Boolean {
if (musicService != null) {
if (playingQueue.size > 0) {
musicService?.addSong(song)
} else {
val queue = ArrayList<Song>()
queue.add(song)
openQueue(queue, 0, false)
}
Toast.makeText(
musicService,
musicService!!.resources.getString(R.string.added_title_to_playing_queue),
Toast.LENGTH_SHORT
).show()
return true
}
return false
}
fun enqueue(songs: List<Song>): Boolean {
if (musicService != null) {
if (playingQueue.size > 0) {
musicService?.addSongs(songs)
} else {
openQueue(songs, 0, false)
}
val toast =
if (songs.size == 1) musicService!!.resources.getString(R.string.added_title_to_playing_queue) else musicService!!.resources.getString(
R.string.added_x_titles_to_playing_queue,
songs.size
)
Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show()
return true
}
return false
}
@JvmStatic
fun removeFromQueue(song: Song): Boolean {
if (musicService != null) {
musicService!!.removeSong(song)
return true
}
return false
}
fun removeFromQueue(position: Int): Boolean {
if (musicService != null && position >= 0 && position < playingQueue.size) {
musicService!!.removeSong(position)
return true
}
return false
}
fun moveSong(from: Int, to: Int): Boolean {
if (musicService != null && from >= 0 && to >= 0 && from < playingQueue.size && to < playingQueue.size) {
musicService!!.moveSong(from, to)
return true
}
return false
}
fun clearQueue(): Boolean {
if (musicService != null) {
musicService!!.clearQueue()
return true
}
return false
}
@JvmStatic
fun playFromUri(uri: Uri) {
if (musicService != null) {
var songs: List<Song>? = null
if (uri.scheme != null && uri.authority != null) {
if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
var songId: String? = null
if (uri.authority == "com.android.providers.media.documents") {
songId = getSongIdFromMediaProvider(uri)
} else if (uri.authority == "media") {
songId = uri.lastPathSegment
}
if (songId != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
musicService!!,
MediaStore.Audio.AudioColumns._ID + "=?",
arrayOf(songId)
)
)
}
}
}
if (songs == null) {
var songFile: File? = null
if (uri.authority != null && uri.authority == "com.android.externalstorage.documents") {
songFile =
File(
Environment.getExternalStorageDirectory(),
uri.path?.split(":".toRegex(), 2)?.get(1)
)
}
if (songFile == null) {
val path = getFilePathFromUri(musicService!!, uri)
if (path != null)
songFile = File(path)
}
if (songFile == null && uri.path != null) {
songFile = File(uri.path)
}
if (songFile != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
musicService!!,
MediaStore.Audio.AudioColumns.DATA + "=?",
arrayOf(songFile.absolutePath)
)
)
}
}
if (songs != null && songs.isNotEmpty()) {
openQueue(songs, 0, true)
} else {
//TODO the file is not listed in the media store
println("The file is not listed in the media store")
}
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private fun getSongIdFromMediaProvider(uri: Uri): String {
return DocumentsContract.getDocumentId(uri).split(":".toRegex())
.dropLastWhile { it.isEmpty() }.toTypedArray()[1]
}
class ServiceBinder internal constructor(private val mCallback: ServiceConnection?) :
ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as MusicService.MusicBinder
musicService = binder.service
mCallback?.onServiceConnected(className, service)
}
override fun onServiceDisconnected(className: ComponentName) {
mCallback?.onServiceDisconnected(className)
musicService = null
}
}
class ServiceToken internal constructor(internal var mWrappedContext: ContextWrapper)
}

View file

@ -0,0 +1,84 @@
/*
* 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 io.github.muntashirakon.music.helper
import android.os.Handler
import android.os.Message
class MusicProgressViewUpdateHelper : Handler {
private var callback: Callback? = null
private var intervalPlaying: Int = 0
private var intervalPaused: Int = 0
fun start() {
queueNextRefresh(1)
}
fun stop() {
removeMessages(CMD_REFRESH_PROGRESS_VIEWS)
}
constructor(callback: Callback) {
this.callback = callback
this.intervalPlaying = UPDATE_INTERVAL_PLAYING
this.intervalPaused = UPDATE_INTERVAL_PAUSED
}
constructor(callback: Callback, intervalPlaying: Int, intervalPaused: Int) {
this.callback = callback
this.intervalPlaying = intervalPlaying
this.intervalPaused = intervalPaused
}
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if (msg.what == CMD_REFRESH_PROGRESS_VIEWS) {
queueNextRefresh(refreshProgressViews().toLong())
}
}
private fun refreshProgressViews(): Int {
val progressMillis = MusicPlayerRemote.songProgressMillis
val totalMillis = MusicPlayerRemote.songDurationMillis
if (totalMillis > 0)
callback?.onUpdateProgressViews(progressMillis, totalMillis)
if (!MusicPlayerRemote.isPlaying) {
return intervalPaused
}
val remainingMillis = intervalPlaying - progressMillis % intervalPlaying
return Math.max(MIN_INTERVAL, remainingMillis)
}
private fun queueNextRefresh(delay: Long) {
val message = obtainMessage(CMD_REFRESH_PROGRESS_VIEWS)
removeMessages(CMD_REFRESH_PROGRESS_VIEWS)
sendMessageDelayed(message, delay)
}
interface Callback {
fun onUpdateProgressViews(progress: Int, total: Int)
}
companion object {
private const val CMD_REFRESH_PROGRESS_VIEWS = 1
private const val MIN_INTERVAL = 20
private const val UPDATE_INTERVAL_PLAYING = 1000
private const val UPDATE_INTERVAL_PAUSED = 500
}
}

View file

@ -0,0 +1,28 @@
/*
* 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 io.github.muntashirakon.music.helper
import android.view.View
class PlayPauseButtonOnClickHandler : View.OnClickListener {
override fun onClick(v: View) {
if (MusicPlayerRemote.isPlaying) {
MusicPlayerRemote.pauseSong()
} else {
MusicPlayerRemote.resumePlaying()
}
}
}

View file

@ -0,0 +1,149 @@
/*
* 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 io.github.muntashirakon.music.helper
import android.app.SearchManager
import android.content.Context
import android.os.Bundle
import android.provider.MediaStore
import io.github.muntashirakon.music.loaders.SongLoader
import io.github.muntashirakon.music.model.Song
import java.util.*
object SearchQueryHelper {
private const val TITLE_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.TITLE + ") = ?"
private const val ALBUM_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.ALBUM + ") = ?"
private const val ARTIST_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.ARTIST + ") = ?"
private const val AND = " AND "
var songs = ArrayList<Song>()
@JvmStatic
fun getSongs(context: Context, extras: Bundle): ArrayList<Song> {
val query = extras.getString(SearchManager.QUERY, null)
val artistName = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST, null)
val albumName = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM, null)
val titleName = extras.getString(MediaStore.EXTRA_MEDIA_TITLE, null)
var songs = ArrayList<Song>()
if (artistName != null && albumName != null && titleName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION,
arrayOf(
artistName.toLowerCase(),
albumName.toLowerCase(),
titleName.toLowerCase()
)
)
)
}
if (songs.isNotEmpty()) {
return songs
}
if (artistName != null && titleName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
ARTIST_SELECTION + AND + TITLE_SELECTION,
arrayOf(artistName.toLowerCase(), titleName.toLowerCase())
)
)
}
if (songs.isNotEmpty()) {
return songs
}
if (albumName != null && titleName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
ALBUM_SELECTION + AND + TITLE_SELECTION,
arrayOf(albumName.toLowerCase(), titleName.toLowerCase())
)
)
}
if (songs.isNotEmpty()) {
return songs
}
if (artistName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
ARTIST_SELECTION,
arrayOf(artistName.toLowerCase())
)
)
}
if (songs.isNotEmpty()) {
return songs
}
if (albumName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
ALBUM_SELECTION,
arrayOf(albumName.toLowerCase())
)
)
}
if (songs.isNotEmpty()) {
return songs
}
if (titleName != null) {
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
TITLE_SELECTION,
arrayOf(titleName.toLowerCase())
)
)
}
if (songs.isNotEmpty()) {
return songs
}
songs =
SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
ARTIST_SELECTION,
arrayOf(query.toLowerCase())
)
)
if (songs.isNotEmpty()) {
return songs
}
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
ALBUM_SELECTION,
arrayOf(query.toLowerCase())
)
)
if (songs.isNotEmpty()) {
return songs
}
songs = SongLoader.getSongs(
SongLoader.makeSongCursor(
context,
TITLE_SELECTION,
arrayOf(query.toLowerCase())
)
)
return if (songs.isNotEmpty()) {
songs
} else ArrayList()
}
}

View file

@ -0,0 +1,32 @@
/*
* 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 io.github.muntashirakon.music.helper
import io.github.muntashirakon.music.model.Song
object ShuffleHelper {
fun makeShuffleList(listToShuffle: MutableList<Song>, current: Int) {
if (listToShuffle.isEmpty()) return
if (current >= 0) {
val song = listToShuffle.removeAt(current)
listToShuffle.shuffle()
listToShuffle.add(0, song)
} else {
listToShuffle.shuffle()
}
}
}

View file

@ -0,0 +1,186 @@
/*
* 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 io.github.muntashirakon.music.helper
import android.provider.MediaStore
class SortOrder {
/**
* Artist sort order entries.
*/
interface ArtistSortOrder {
companion object {
/* Artist sort order A-Z */
const val ARTIST_A_Z = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER
/* Artist sort order Z-A */
const val ARTIST_Z_A = "$ARTIST_A_Z DESC"
/* Artist sort order number of songs */
const val ARTIST_NUMBER_OF_SONGS = MediaStore.Audio.Artists.NUMBER_OF_TRACKS + " DESC"
/* Artist sort order number of albums */
const val ARTIST_NUMBER_OF_ALBUMS = MediaStore.Audio.Artists.NUMBER_OF_ALBUMS + " DESC"
}
}
/**
* Album sort order entries.
*/
interface AlbumSortOrder {
companion object {
/* Album sort order A-Z */
const val ALBUM_A_Z = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER
/* Album sort order Z-A */
const val ALBUM_Z_A = "$ALBUM_A_Z DESC"
/* Album sort order songs */
const val ALBUM_NUMBER_OF_SONGS = MediaStore.Audio.Albums.NUMBER_OF_SONGS + " DESC"
/* Album sort order artist */
const val ALBUM_ARTIST = (MediaStore.Audio.Artists.DEFAULT_SORT_ORDER
+ ", " + MediaStore.Audio.Albums.DEFAULT_SORT_ORDER)
/* Album sort order year */
const val ALBUM_YEAR = MediaStore.Audio.Media.YEAR + " DESC"
}
}
/**
* Song sort order entries.
*/
interface SongSortOrder {
companion object {
/* Song sort order A-Z */
const val SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER
/* Song sort order Z-A */
const val SONG_Z_A = "$SONG_A_Z DESC"
/* Song sort order artist */
const val SONG_ARTIST = MediaStore.Audio.Artists.DEFAULT_SORT_ORDER
/* Song sort order album */
const val SONG_ALBUM = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER
/* Song sort order year */
const val SONG_YEAR = MediaStore.Audio.Media.YEAR + " DESC"
/* Song sort order duration */
const val SONG_DURATION = MediaStore.Audio.Media.DURATION + " DESC"
/* Song sort order date */
const val SONG_DATE = MediaStore.Audio.Media.DATE_ADDED + " DESC"
/* Song sort modified date */
const val SONG_DATE_MODIFIED = MediaStore.Audio.Media.DATE_MODIFIED + " DESC"
/* Song sort order composer*/
const val COMPOSER = MediaStore.Audio.Media.COMPOSER
}
}
/**
* Album song sort order entries.
*/
interface AlbumSongSortOrder {
companion object {
/* Album song sort order A-Z */
const val SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER
/* Album song sort order Z-A */
const val SONG_Z_A = "$SONG_A_Z DESC"
/* Album song sort order track list */
const val SONG_TRACK_LIST = (MediaStore.Audio.Media.TRACK + ", "
+ MediaStore.Audio.Media.DEFAULT_SORT_ORDER)
/* Album song sort order duration */
const val SONG_DURATION = SongSortOrder.SONG_DURATION
}
}
/**
* Artist song sort order entries.
*/
interface ArtistSongSortOrder {
companion object {
/* Artist song sort order A-Z */
const val SONG_A_Z = MediaStore.Audio.Media.DEFAULT_SORT_ORDER
/* Artist song sort order Z-A */
const val SONG_Z_A = "$SONG_A_Z DESC"
/* Artist song sort order album */
const val SONG_ALBUM = MediaStore.Audio.Media.ALBUM
/* Artist song sort order year */
const val SONG_YEAR = MediaStore.Audio.Media.YEAR + " DESC"
/* Artist song sort order duration */
const val SONG_DURATION = MediaStore.Audio.Media.DURATION + " DESC"
/* Artist song sort order date */
const val SONG_DATE = MediaStore.Audio.Media.DATE_ADDED + " DESC"
}
}
/**
* Artist album sort order entries.
*/
interface ArtistAlbumSortOrder {
companion object {
/* Artist album sort order A-Z */
const val ALBUM_A_Z = MediaStore.Audio.Albums.DEFAULT_SORT_ORDER
/* Artist album sort order Z-A */
const val ALBUM_Z_A = "$ALBUM_A_Z DESC"
/* Artist album sort order year */
const val ALBUM_YEAR = MediaStore.Audio.Media.YEAR + " DESC"
/* Artist album sort order year */
const val ALBUM_YEAR_ASC = MediaStore.Audio.Media.YEAR + " ASC"
}
}
/**
* Genre sort order entries.
*/
interface GenreSortOrder {
companion object {
/* Genre sort order A-Z */
const val GENRE_A_Z = MediaStore.Audio.Genres.DEFAULT_SORT_ORDER
/* Genre sort order Z-A */
const val ALBUM_Z_A = "$GENRE_A_Z DESC"
}
}
}

View file

@ -0,0 +1,333 @@
package io.github.muntashirakon.music.helper;
import android.graphics.Bitmap;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Blur using Java code.
* <p/>
* This is a compromise between Gaussian Blur and Box blur
* It creates much better looking blurs than Box Blur, but is
* 7x faster than my Gaussian Blur implementation.
* <p/>
* I called it Stack Blur because this describes best how this
* filter works internally: it creates a kind of moving stack
* of colors whilst scanning through the image. Thereby it
* just has to add one new block of color to the right side
* of the stack and remove the leftmost color. The remaining
* colors on the topmost layer of the stack are either added on
* or reduced by one, depending on if they are on the right or
* on the left side of the stack.
*
* @author Enrique López Mañas <eenriquelopez@gmail.com>
* http://www.neo-tech.es
* <p/>
* Author of the original algorithm: Mario Klingemann <mario.quasimondo.com>
* <p/>
* Based heavily on http://vitiy.info/Code/stackblur.cpp
* See http://vitiy.info/stackblur-algorithm-multi-threaded-blur-for-cpp/
* @copyright: Enrique López Mañas
* @license: Apache License 2.0
*/
public class StackBlur {
static final int EXECUTOR_THREADS = Runtime.getRuntime().availableProcessors();
static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(EXECUTOR_THREADS);
private static final short[] stackblur_mul = {
512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512,
454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512,
482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456,
437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512,
497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328,
320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456,
446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335,
329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512,
505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405,
399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328,
324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271,
268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456,
451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388,
385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335,
332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292,
289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259
};
private static final byte[] stackblur_shr = {
9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24
};
public static Bitmap blur(Bitmap original, float radius) {
int w = original.getWidth();
int h = original.getHeight();
int[] currentPixels = new int[w * h];
original.getPixels(currentPixels, 0, w, 0, 0, w, h);
int cores = EXECUTOR_THREADS;
ArrayList<BlurTask> horizontal = new ArrayList<BlurTask>(cores);
ArrayList<BlurTask> vertical = new ArrayList<BlurTask>(cores);
for (int i = 0; i < cores; i++) {
horizontal.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 1));
vertical.add(new BlurTask(currentPixels, w, h, (int) radius, cores, i, 2));
}
try {
EXECUTOR.invokeAll(horizontal);
} catch (InterruptedException e) {
return null;
}
try {
EXECUTOR.invokeAll(vertical);
} catch (InterruptedException e) {
return null;
}
return Bitmap.createBitmap(currentPixels, w, h, Bitmap.Config.ARGB_8888);
}
private static void blurIteration(int[] src, int w, int h, int radius, int cores, int core, int step) {
int x, y, xp, yp, i;
int sp;
int stack_start;
int stack_i;
int src_i;
int dst_i;
long sum_r, sum_g, sum_b,
sum_in_r, sum_in_g, sum_in_b,
sum_out_r, sum_out_g, sum_out_b;
int wm = w - 1;
int hm = h - 1;
int div = (radius * 2) + 1;
int mul_sum = stackblur_mul[radius];
byte shr_sum = stackblur_shr[radius];
int[] stack = new int[div];
if (step == 1) {
int minY = core * h / cores;
int maxY = (core + 1) * h / cores;
for (y = minY; y < maxY; y++) {
sum_r = sum_g = sum_b =
sum_in_r = sum_in_g = sum_in_b =
sum_out_r = sum_out_g = sum_out_b = 0;
src_i = w * y; // start of line (0,y)
for (i = 0; i <= radius; i++) {
stack_i = i;
stack[stack_i] = src[src_i];
sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1);
sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1);
sum_b += (src[src_i] & 0xff) * (i + 1);
sum_out_r += ((src[src_i] >>> 16) & 0xff);
sum_out_g += ((src[src_i] >>> 8) & 0xff);
sum_out_b += (src[src_i] & 0xff);
}
for (i = 1; i <= radius; i++) {
if (i <= wm) src_i += 1;
stack_i = i + radius;
stack[stack_i] = src[src_i];
sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i);
sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i);
sum_b += (src[src_i] & 0xff) * (radius + 1 - i);
sum_in_r += ((src[src_i] >>> 16) & 0xff);
sum_in_g += ((src[src_i] >>> 8) & 0xff);
sum_in_b += (src[src_i] & 0xff);
}
sp = radius;
xp = radius;
if (xp > wm) xp = wm;
src_i = xp + y * w; // img.pix_ptr(xp, y);
dst_i = y * w; // img.pix_ptr(0, y);
for (x = 0; x < w; x++) {
src[dst_i] = (int)
((src[dst_i] & 0xFFFFFFFF) |
((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) |
((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) |
((((sum_b * mul_sum) >>> shr_sum) & 0xff)));
dst_i += 1;
sum_r -= sum_out_r;
sum_g -= sum_out_g;
sum_b -= sum_out_b;
stack_start = sp + div - radius;
if (stack_start >= div) stack_start -= div;
stack_i = stack_start;
sum_out_r -= ((stack[stack_i] >>> 16) & 0xff);
sum_out_g -= ((stack[stack_i] >>> 8) & 0xff);
sum_out_b -= (stack[stack_i] & 0xff);
if (xp < wm) {
src_i += 1;
++xp;
}
stack[stack_i] = src[src_i];
sum_in_r += ((src[src_i] >>> 16) & 0xff);
sum_in_g += ((src[src_i] >>> 8) & 0xff);
sum_in_b += (src[src_i] & 0xff);
sum_r += sum_in_r;
sum_g += sum_in_g;
sum_b += sum_in_b;
++sp;
if (sp >= div) sp = 0;
stack_i = sp;
sum_out_r += ((stack[stack_i] >>> 16) & 0xff);
sum_out_g += ((stack[stack_i] >>> 8) & 0xff);
sum_out_b += (stack[stack_i] & 0xff);
sum_in_r -= ((stack[stack_i] >>> 16) & 0xff);
sum_in_g -= ((stack[stack_i] >>> 8) & 0xff);
sum_in_b -= (stack[stack_i] & 0xff);
}
}
}
// step 2
else if (step == 2) {
int minX = core * w / cores;
int maxX = (core + 1) * w / cores;
for (x = minX; x < maxX; x++) {
sum_r = sum_g = sum_b =
sum_in_r = sum_in_g = sum_in_b =
sum_out_r = sum_out_g = sum_out_b = 0;
src_i = x; // x,0
for (i = 0; i <= radius; i++) {
stack_i = i;
stack[stack_i] = src[src_i];
sum_r += ((src[src_i] >>> 16) & 0xff) * (i + 1);
sum_g += ((src[src_i] >>> 8) & 0xff) * (i + 1);
sum_b += (src[src_i] & 0xff) * (i + 1);
sum_out_r += ((src[src_i] >>> 16) & 0xff);
sum_out_g += ((src[src_i] >>> 8) & 0xff);
sum_out_b += (src[src_i] & 0xff);
}
for (i = 1; i <= radius; i++) {
if (i <= hm) src_i += w; // +stride
stack_i = i + radius;
stack[stack_i] = src[src_i];
sum_r += ((src[src_i] >>> 16) & 0xff) * (radius + 1 - i);
sum_g += ((src[src_i] >>> 8) & 0xff) * (radius + 1 - i);
sum_b += (src[src_i] & 0xff) * (radius + 1 - i);
sum_in_r += ((src[src_i] >>> 16) & 0xff);
sum_in_g += ((src[src_i] >>> 8) & 0xff);
sum_in_b += (src[src_i] & 0xff);
}
sp = radius;
yp = radius;
if (yp > hm) yp = hm;
src_i = x + yp * w; // img.pix_ptr(x, yp);
dst_i = x; // img.pix_ptr(x, 0);
for (y = 0; y < h; y++) {
src[dst_i] = (int)
((src[dst_i] & 0xFFFFFFFF) |
((((sum_r * mul_sum) >>> shr_sum) & 0xff) << 16) |
((((sum_g * mul_sum) >>> shr_sum) & 0xff) << 8) |
((((sum_b * mul_sum) >>> shr_sum) & 0xff)));
dst_i += w;
sum_r -= sum_out_r;
sum_g -= sum_out_g;
sum_b -= sum_out_b;
stack_start = sp + div - radius;
if (stack_start >= div) stack_start -= div;
stack_i = stack_start;
sum_out_r -= ((stack[stack_i] >>> 16) & 0xff);
sum_out_g -= ((stack[stack_i] >>> 8) & 0xff);
sum_out_b -= (stack[stack_i] & 0xff);
if (yp < hm) {
src_i += w; // stride
++yp;
}
stack[stack_i] = src[src_i];
sum_in_r += ((src[src_i] >>> 16) & 0xff);
sum_in_g += ((src[src_i] >>> 8) & 0xff);
sum_in_b += (src[src_i] & 0xff);
sum_r += sum_in_r;
sum_g += sum_in_g;
sum_b += sum_in_b;
++sp;
if (sp >= div) sp = 0;
stack_i = sp;
sum_out_r += ((stack[stack_i] >>> 16) & 0xff);
sum_out_g += ((stack[stack_i] >>> 8) & 0xff);
sum_out_b += (stack[stack_i] & 0xff);
sum_in_r -= ((stack[stack_i] >>> 16) & 0xff);
sum_in_g -= ((stack[stack_i] >>> 8) & 0xff);
sum_in_b -= (stack[stack_i] & 0xff);
}
}
}
}
private static class BlurTask implements Callable<Void> {
private final int[] _src;
private final int _w;
private final int _h;
private final int _radius;
private final int _totalCores;
private final int _coreIndex;
private final int _round;
public BlurTask(int[] src, int w, int h, int radius, int totalCores, int coreIndex, int round) {
_src = src;
_w = w;
_h = h;
_radius = radius;
_totalCores = totalCores;
_coreIndex = coreIndex;
_round = round;
}
@Override
public Void call() throws Exception {
blurIteration(_src, _w, _h, _radius, _totalCores, _coreIndex, _round);
return null;
}
}
}

View file

@ -0,0 +1,94 @@
/*
* 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 io.github.muntashirakon.music.helper
/**
* Simple thread safe stop watch.
*
* @author Karim Abou Zeid (kabouzeid)
*/
class StopWatch {
/**
* The time the stop watch was last started.
*/
private var startTime: Long = 0
/**
* The time elapsed before the current [.startTime].
*/
private var previousElapsedTime: Long = 0
/**
* Whether the stop watch is currently running or not.
*/
private var isRunning: Boolean = false
/**
* @return the total elapsed time in milliseconds
*/
val elapsedTime: Long
get() = synchronized(this) {
var currentElapsedTime: Long = 0
if (isRunning) {
currentElapsedTime = System.currentTimeMillis() - startTime
}
return previousElapsedTime + currentElapsedTime
}
/**
* Starts or continues the stop watch.
*
* @see .pause
* @see .reset
*/
fun start() {
synchronized(this) {
startTime = System.currentTimeMillis()
isRunning = true
}
}
/**
* Pauses the stop watch. It can be continued later from [.start].
*
* @see .start
* @see .reset
*/
fun pause() {
synchronized(this) {
previousElapsedTime += System.currentTimeMillis() - startTime
isRunning = false
}
}
/**
* Stops and resets the stop watch to zero milliseconds.
*
* @see .start
* @see .pause
*/
fun reset() {
synchronized(this) {
startTime = 0
previousElapsedTime = 0
isRunning = false
}
}
override fun toString(): String {
return String.format("%d millis", elapsedTime)
}
}

View file

@ -0,0 +1,56 @@
/*
* 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 io.github.muntashirakon.music.helper.menu
import android.app.Activity
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.loaders.GenreLoader
import io.github.muntashirakon.music.model.Genre
import io.github.muntashirakon.music.model.Song
import java.util.*
object GenreMenuHelper {
fun handleMenuClick(activity: AppCompatActivity, genre: Genre, item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_play -> {
MusicPlayerRemote.openQueue(getGenreSongs(activity, genre), 0, true)
return true
}
R.id.action_play_next -> {
MusicPlayerRemote.playNext(getGenreSongs(activity, genre))
return true
}
R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(getGenreSongs(activity, genre))
.show(activity.supportFragmentManager, "ADD_PLAYLIST")
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(getGenreSongs(activity, genre))
return true
}
}
return false
}
private fun getGenreSongs(activity: Activity, genre: Genre): ArrayList<Song> {
return GenreLoader.getSongs(activity, genre.id)
}
}

View file

@ -0,0 +1,111 @@
/*
* 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 io.github.muntashirakon.music.helper.menu
import android.app.Activity
import android.content.Context
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import io.github.muntashirakon.music.App
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog
import io.github.muntashirakon.music.dialogs.DeletePlaylistDialog
import io.github.muntashirakon.music.dialogs.RenamePlaylistDialog
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.loaders.PlaylistSongsLoader
import io.github.muntashirakon.music.misc.WeakContextAsyncTask
import io.github.muntashirakon.music.model.AbsCustomPlaylist
import io.github.muntashirakon.music.model.Playlist
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.PlaylistsUtil
import java.util.*
object PlaylistMenuHelper {
fun handleMenuClick(
activity: AppCompatActivity,
playlist: Playlist, item: MenuItem
): Boolean {
when (item.itemId) {
R.id.action_play -> {
MusicPlayerRemote.openQueue(getPlaylistSongs(activity, playlist), 9, true)
return true
}
R.id.action_play_next -> {
MusicPlayerRemote.playNext(getPlaylistSongs(activity, playlist))
return true
}
R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(getPlaylistSongs(activity, playlist))
.show(activity.supportFragmentManager, "ADD_PLAYLIST")
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(getPlaylistSongs(activity, playlist))
return true
}
R.id.action_rename_playlist -> {
RenamePlaylistDialog.create(playlist.id.toLong())
.show(activity.supportFragmentManager, "RENAME_PLAYLIST")
return true
}
R.id.action_delete_playlist -> {
DeletePlaylistDialog.create(playlist)
.show(activity.supportFragmentManager, "DELETE_PLAYLIST")
return true
}
R.id.action_save_playlist -> {
SavePlaylistAsyncTask(activity).execute(playlist)
return true
}
}
return false
}
private fun getPlaylistSongs(
activity: Activity,
playlist: Playlist
): ArrayList<Song> {
return if (playlist is AbsCustomPlaylist) {
playlist.getSongs(activity)
} else {
PlaylistSongsLoader.getPlaylistSongList(activity, playlist)
}
}
private class SavePlaylistAsyncTask internal constructor(context: Context) :
WeakContextAsyncTask<Playlist, String, String>(context) {
override fun doInBackground(vararg params: Playlist): String {
return String.format(
App.getContext().getString(
R.string
.saved_playlist_to
), PlaylistsUtil.savePlaylist(App.getContext(), params[0])
)
}
override fun onPostExecute(string: String) {
super.onPostExecute(string)
val context = context
if (context != null) {
Toast.makeText(context, string, Toast.LENGTH_LONG).show()
}
}
}
}

View file

@ -0,0 +1,122 @@
/*
* 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 io.github.muntashirakon.music.helper.menu
import android.content.Intent
import android.view.MenuItem
import android.view.View
import android.widget.PopupMenu
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.activities.tageditor.AbsTagEditorActivity
import io.github.muntashirakon.music.activities.tageditor.SongTagEditorActivity
import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog
import io.github.muntashirakon.music.dialogs.DeleteSongsDialog
import io.github.muntashirakon.music.dialogs.SongDetailDialog
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.interfaces.PaletteColorHolder
import io.github.muntashirakon.music.model.Song
import io.github.muntashirakon.music.util.MusicUtil
import io.github.muntashirakon.music.util.NavigationUtil
import io.github.muntashirakon.music.util.RingtoneManager
object SongMenuHelper {
const val MENU_RES = R.menu.menu_item_song
fun handleMenuClick(activity: FragmentActivity, song: Song, menuItemId: Int): Boolean {
when (menuItemId) {
R.id.action_set_as_ringtone -> {
if (RingtoneManager.requiresDialog(activity)) {
RingtoneManager.getDialog(activity)
} else {
val ringtoneManager = RingtoneManager(activity)
ringtoneManager.setRingtone(song)
}
return true
}
R.id.action_share -> {
activity.startActivity(
Intent.createChooser(
MusicUtil.createShareSongFileIntent(song, activity),
null
)
)
return true
}
R.id.action_delete_from_device -> {
DeleteSongsDialog.create(song).show(activity.supportFragmentManager, "DELETE_SONGS")
return true
}
R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(song)
.show(activity.supportFragmentManager, "ADD_PLAYLIST")
return true
}
R.id.action_play_next -> {
MusicPlayerRemote.playNext(song)
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(song)
return true
}
R.id.action_tag_editor -> {
val tagEditorIntent = Intent(activity, SongTagEditorActivity::class.java)
tagEditorIntent.putExtra(AbsTagEditorActivity.EXTRA_ID, song.id)
if (activity is PaletteColorHolder)
tagEditorIntent.putExtra(
AbsTagEditorActivity.EXTRA_PALETTE,
(activity as PaletteColorHolder).paletteColor
)
activity.startActivity(tagEditorIntent)
return true
}
R.id.action_details -> {
SongDetailDialog.create(song).show(activity.supportFragmentManager, "SONG_DETAILS")
return true
}
R.id.action_go_to_album -> {
NavigationUtil.goToAlbum(activity, song.albumId)
return true
}
R.id.action_go_to_artist -> {
NavigationUtil.goToArtist(activity, song.artistId)
return true
}
}
return false
}
abstract class OnClickSongMenu protected constructor(private val activity: AppCompatActivity) :
View.OnClickListener, PopupMenu.OnMenuItemClickListener {
open val menuRes: Int
get() = MENU_RES
abstract val song: Song
override fun onClick(v: View) {
val popupMenu = PopupMenu(activity, v)
popupMenu.inflate(menuRes)
popupMenu.setOnMenuItemClickListener(this)
popupMenu.show()
}
override fun onMenuItemClick(item: MenuItem): Boolean {
return handleMenuClick(activity, song, item.itemId)
}
}
}

View file

@ -0,0 +1,55 @@
/*
* 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 io.github.muntashirakon.music.helper.menu
import androidx.fragment.app.FragmentActivity
import io.github.muntashirakon.music.R
import io.github.muntashirakon.music.dialogs.AddToPlaylistDialog
import io.github.muntashirakon.music.dialogs.DeleteSongsDialog
import io.github.muntashirakon.music.helper.MusicPlayerRemote
import io.github.muntashirakon.music.model.Song
import java.util.*
object SongsMenuHelper {
fun handleMenuClick(
activity: FragmentActivity,
songs: ArrayList<Song>,
menuItemId: Int
): Boolean {
when (menuItemId) {
R.id.action_play_next -> {
MusicPlayerRemote.playNext(songs)
return true
}
R.id.action_add_to_current_playing -> {
MusicPlayerRemote.enqueue(songs)
return true
}
R.id.action_add_to_playlist -> {
AddToPlaylistDialog.create(songs)
.show(activity.supportFragmentManager, "ADD_PLAYLIST")
return true
}
R.id.action_delete_from_device -> {
DeleteSongsDialog.create(songs)
.show(activity.supportFragmentManager, "DELETE_SONGS")
return true
}
}
return false
}
}