playback: use single-queue system

Switch auxio to a single-queue system. "Play next" adds songs to the top
of the queue, similar to before, and then "Add to queue" adds songs to
the bottom of the queue. This enables many more enhancements to be made
to the playback experience, at the cost of a feature I preferred.

Resolves #44.
This commit is contained in:
OxygenCobalt 2021-12-22 16:31:26 -07:00
parent b92b08f8ab
commit 25dd276bd8
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
43 changed files with 187 additions and 478 deletions

View file

@ -29,7 +29,6 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.oxycblt.auxio.databinding.FragmentMainBinding import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.detail.DetailViewModel import org.oxycblt.auxio.detail.DetailViewModel

View file

@ -66,7 +66,7 @@ class AlbumDetailFragment : DetailFragment() {
setupToolbar(detailModel.curAlbum.value!!, R.menu.menu_album_detail) { itemId -> setupToolbar(detailModel.curAlbum.value!!, R.menu.menu_album_detail) { itemId ->
if (itemId == R.id.action_queue_add) { if (itemId == R.id.action_queue_add) {
playbackModel.addToUserQueue(detailModel.curAlbum.value!!) playbackModel.playNext(detailModel.curAlbum.value!!)
requireContext().showToast(R.string.lbl_queue_added) requireContext().showToast(R.string.lbl_queue_added)
true true
} else { } else {
@ -147,12 +147,6 @@ class AlbumDetailFragment : DetailFragment() {
} }
} }
playbackModel.isInUserQueue.observe(viewLifecycleOwner) { inUserQueue ->
if (inUserQueue) {
detailAdapter.highlightSong(null, binding.detailRecycler)
}
}
logD("Fragment created.") logD("Fragment created.")
return binding.root return binding.root

View file

@ -115,12 +115,6 @@ class GenreDetailFragment : DetailFragment() {
} }
} }
playbackModel.isInUserQueue.observe(viewLifecycleOwner) { inUserQueue ->
if (inUserQueue) {
detailAdapter.highlightSong(null, binding.detailRecycler)
}
}
logD("Fragment created.") logD("Fragment created.")
return binding.root return binding.root

View file

@ -15,6 +15,7 @@ class AdaptiveFloatingActionButton @JvmOverloads constructor(
size = SIZE_NORMAL size = SIZE_NORMAL
if (resources.configuration.smallestScreenWidthDp >= 640) { if (resources.configuration.smallestScreenWidthDp >= 640) {
// Use a large FAB on large screens, as it makes it easier to touch.
customSize = resources.getDimensionPixelSize(MaterialR.dimen.m3_large_fab_size) customSize = resources.getDimensionPixelSize(MaterialR.dimen.m3_large_fab_size)
setMaxImageSize( setMaxImageSize(
resources.getDimensionPixelSize(MaterialR.dimen.m3_large_fab_max_image_size) resources.getDimensionPixelSize(MaterialR.dimen.m3_large_fab_max_image_size)

View file

@ -131,8 +131,10 @@ class PlaybackFragment : Fragment() {
binding.playbackSeekBar.setProgress(pos) binding.playbackSeekBar.setProgress(pos)
} }
playbackModel.displayQueue.observe(viewLifecycleOwner) { playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) {
updateQueueIcon(queueItem) // The queue icon uses a selector that will automatically tint the icon as active or
// inactive. We just need to set the flag.
queueItem.isEnabled = playbackModel.nextItemsInQueue.value!!.isNotEmpty()
} }
playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying -> playbackModel.isPlaying.observe(viewLifecycleOwner) { isPlaying ->
@ -155,13 +157,4 @@ class PlaybackFragment : Fragment() {
// so we can't really do much // so we can't really do much
(requireView().parent.parent.parent as PlaybackLayout).collapse() (requireView().parent.parent.parent as PlaybackLayout).collapse()
} }
private fun updateQueueIcon(queueItem: MenuItem) {
val userQueue = playbackModel.userQueue.value!!
val nextQueue = playbackModel.nextItemsInQueue.value!!
// The queue icon uses a selector that will automatically tint the icon as active or
// inactive. We just need to set the flag.
queueItem.isEnabled = !(userQueue.isEmpty() && nextQueue.isEmpty())
}
} }

View file

@ -21,24 +21,17 @@ package org.oxycblt.auxio.playback
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.ActionHeader
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.HeaderString
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.queue.QueueAdapter
import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.LoopMode
import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
@ -61,7 +54,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
// Queue // Queue
private val mQueue = MutableLiveData(listOf<Song>()) private val mQueue = MutableLiveData(listOf<Song>())
private val mUserQueue = MutableLiveData(listOf<Song>())
private val mIndex = MutableLiveData(0) private val mIndex = MutableLiveData(0)
private val mMode = MutableLiveData(PlaybackMode.ALL_SONGS) private val mMode = MutableLiveData(PlaybackMode.ALL_SONGS)
@ -69,7 +61,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
private val mIsPlaying = MutableLiveData(false) private val mIsPlaying = MutableLiveData(false)
private val mIsShuffling = MutableLiveData(false) private val mIsShuffling = MutableLiveData(false)
private val mLoopMode = MutableLiveData(LoopMode.NONE) private val mLoopMode = MutableLiveData(LoopMode.NONE)
private val mIsInUserQueue = MutableLiveData(false)
// Other // Other
private var mIntentUri: Uri? = null private var mIntentUri: Uri? = null
@ -83,12 +74,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
/** The current queue determined by [playbackMode] and [parent] */ /** The current queue determined by [playbackMode] and [parent] */
val queue: LiveData<List<Song>> get() = mQueue val queue: LiveData<List<Song>> get() = mQueue
/** The queue created by the user. */
val userQueue: LiveData<List<Song>> get() = mUserQueue
/** The current [PlaybackMode] that also determines the queue */ /** The current [PlaybackMode] that also determines the queue */
val playbackMode: LiveData<PlaybackMode> get() = mMode val playbackMode: LiveData<PlaybackMode> get() = mMode
/** Whether playback is originating from the user-generated queue or not */
val isInUserQueue: LiveData<Boolean> = mIsInUserQueue
val isPlaying: LiveData<Boolean> get() = mIsPlaying val isPlaying: LiveData<Boolean> get() = mIsPlaying
val isShuffling: LiveData<Boolean> get() = mIsShuffling val isShuffling: LiveData<Boolean> get() = mIsShuffling
@ -100,62 +87,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
queue.slice((mIndex.value!! + 1) until queue.size) queue.slice((mIndex.value!! + 1) until queue.size)
} }
/** The combined queue data used for UIs, with header data included */
val displayQueue = MediatorLiveData<List<BaseModel>>().apply {
val combine: (userQueue: List<Song>, nextQueue: List<Song>) -> List<BaseModel> =
{ userQueue, nextQueue ->
val queue = mutableListOf<BaseModel>()
if (userQueue.isNotEmpty()) {
queue += ActionHeader(
id = -2,
string = HeaderString.Single(R.string.lbl_next_user_queue),
icon = R.drawable.ic_clear,
desc = R.string.desc_clear_user_queue,
onClick = { playbackManager.clearUserQueue() }
)
queue += userQueue
}
if (nextQueue.isNotEmpty()) {
val parentName = parent.value?.name
queue += Header(
id = -3,
string = HeaderString.WithArg(
R.string.fmt_next_from,
if (parentName != null) {
HeaderString.Arg.Value(parentName)
} else {
HeaderString.Arg.Resource(R.string.lbl_all_songs)
}
)
)
queue += nextQueue
}
queue
}
// Do not move these around. The transformed value must be generated through this
// observer call first before the userQueue source uses it assuming that it's not
// null.
addSource(nextItemsInQueue) { nextQueue ->
value = combine(userQueue.value!!, nextQueue)
}
addSource(userQueue) { userQueue ->
value = combine(
userQueue,
requireNotNull(nextItemsInQueue.value) {
"Transformed value was not generated yet."
}
)
}
}
private val playbackManager = PlaybackStateManager.maybeGetInstance() private val playbackManager = PlaybackStateManager.maybeGetInstance()
private val settingsManager = SettingsManager.getInstance() private val settingsManager = SettingsManager.getInstance()
@ -285,95 +216,65 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
} }
/** /**
* Remove an item at [adapterIndex], being a non-header data index. * Remove a queue item using it's recyclerview adapter index. If the indices are valid,
* @param queueAdapter [QueueAdapter] instance to push changes to when successful. * [apply] is called just before the change is committed so that the adapter can be updated.
*/ */
fun removeQueueDataItem(adapterIndex: Int, queueAdapter: QueueAdapter) { fun removeQueueDataItem(adapterIndex: Int, apply: () -> Unit) {
var index = adapterIndex.dec() val adjusted = adapterIndex + (mQueue.value!!.size - nextItemsInQueue.value!!.size)
// If the item is in the user queue, then remove it from there after accounting for the header. if (adjusted in mQueue.value!!.indices) {
if (index < mUserQueue.value!!.size) { apply()
queueAdapter.removeItem(adapterIndex) playbackManager.removeQueueItem(adjusted)
playbackManager.removeUserQueueItem(index)
} else {
// Translate the indices into proper queue indices if removing an item from there.
index += (mQueue.value!!.size - nextItemsInQueue.value!!.size)
if (userQueue.value!!.isNotEmpty()) {
index -= mUserQueue.value!!.size.inc()
}
queueAdapter.removeItem(adapterIndex)
playbackManager.removeQueueItem(index)
} }
} }
/** /**
* Move a queue OR user queue item from [adapterFrom] to [adapterTo], as long as both * Move queue items using their recyclerview adapter indices. If the indices are valid,
* indices are non-header data indices. * [apply] is called just before the change is committed so that the adapter can be updated.
* @param queueAdapter [QueueAdapter] instance to push changes to when successful.
*/ */
fun moveQueueDataItems( fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int, apply: () -> Unit): Boolean {
adapterFrom: Int, val delta = (mQueue.value!!.size - nextItemsInQueue.value!!.size)
adapterTo: Int,
queueAdapter: QueueAdapter
): Boolean {
var from = adapterFrom.dec()
var to = adapterTo.dec()
if (from < mUserQueue.value!!.size) { val from = adapterFrom + delta
// Ignore invalid movements to out of bounds, header, or queue positions val to = adapterTo + delta
if (to >= mUserQueue.value!!.size || to < 0) return false
queueAdapter.moveItems(adapterFrom, adapterTo)
playbackManager.moveUserQueueItems(from, to)
} else {
// Ignore invalid movements to out of bounds or header positions
if (to < 0) return false
// Get the real queue positions from the nextInQueue positions
val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size
from += delta
to += delta
if (userQueue.value!!.isNotEmpty()) {
// Ignore user queue positions
if (to <= mUserQueue.value!!.size.inc()) return false
from -= mUserQueue.value!!.size.inc()
to -= mUserQueue.value!!.size.inc()
// Ignore movements that are past the next songs
if (to <= mIndex.value!!) return false
}
queueAdapter.moveItems(adapterFrom, adapterTo)
if (from in mQueue.value!!.indices && to in mQueue.value!!.indices) {
apply()
playbackManager.moveQueueItems(from, to) playbackManager.moveQueueItems(from, to)
return true
} }
return true return false
} }
/** /**
* Add a [Song] to the user queue. * Add a [Song] to the top of the queue.
*/ */
fun addToUserQueue(song: Song) { fun playNext(song: Song) {
playbackManager.addToUserQueue(song) playbackManager.playNext(song)
} }
/** /**
* Add an [Album] to the user queue * Add an [Album] to the top of the queue.
*/ */
fun addToUserQueue(album: Album) { fun playNext(album: Album) {
playbackManager.addToUserQueue(settingsManager.detailAlbumSort.sortAlbum(album)) playbackManager.playNext(settingsManager.detailAlbumSort.sortAlbum(album))
} }
// --- STATUS FUNCTIONS --- /**
* Add a [Song] to the end of the queue.
*/
fun addToQueue(song: Song) {
playbackManager.addToQueue(song)
}
/**
* Add an [Album] to the end of the queue.
*/
fun addToQueue(album: Album) {
playbackManager.addToQueue(settingsManager.detailAlbumSort.sortAlbum(album))
}
// --- STATUS FUNCTIONS ---
/** /**
* Flip the playing status, e.g from playing to paused * Flip the playing status, e.g from playing to paused
@ -405,7 +306,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
fun savePlaybackState(context: Context, onDone: () -> Unit) { fun savePlaybackState(context: Context, onDone: () -> Unit) {
viewModelScope.launch { viewModelScope.launch {
playbackManager.saveStateToDatabase(context) playbackManager.saveStateToDatabase(context)
onDone() onDone()
} }
} }
@ -445,7 +345,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
mParent.value = playbackManager.parent mParent.value = playbackManager.parent
mQueue.value = playbackManager.queue mQueue.value = playbackManager.queue
mMode.value = playbackManager.playbackMode mMode.value = playbackManager.playbackMode
mUserQueue.value = playbackManager.userQueue
mIndex.value = playbackManager.index mIndex.value = playbackManager.index
mIsPlaying.value = playbackManager.isPlaying mIsPlaying.value = playbackManager.isPlaying
mIsShuffling.value = playbackManager.isShuffling mIsShuffling.value = playbackManager.isShuffling
@ -474,10 +373,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
mQueue.value = queue mQueue.value = queue
} }
override fun onUserQueueUpdate(userQueue: List<Song>) {
mUserQueue.value = userQueue
}
override fun onIndexUpdate(index: Int) { override fun onIndexUpdate(index: Int) {
mIndex.value = index mIndex.value = index
} }
@ -497,8 +392,4 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
override fun onLoopUpdate(loopMode: LoopMode) { override fun onLoopUpdate(loopMode: LoopMode) {
mLoopMode.value = loopMode mLoopMode.value = loopMode
} }
override fun onInUserQueueUpdate(isInUserQueue: Boolean) {
mIsInUserQueue.value = isInUserQueue
}
} }

View file

@ -39,7 +39,6 @@ import org.oxycblt.auxio.ui.BaseViewHolder
import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.DiffCallback
import org.oxycblt.auxio.ui.HeaderViewHolder import org.oxycblt.auxio.ui.HeaderViewHolder
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
/** /**
@ -105,9 +104,7 @@ class QueueAdapter(
* Used since [submitList] will cause QueueAdapter to freak out. * Used since [submitList] will cause QueueAdapter to freak out.
*/ */
fun moveItems(adapterFrom: Int, adapterTo: Int) { fun moveItems(adapterFrom: Int, adapterTo: Int) {
val item = data.removeAt(adapterFrom) data.add(adapterTo, data.removeAt(adapterFrom))
data.add(adapterTo, item)
notifyItemMoved(adapterFrom, adapterTo) notifyItemMoved(adapterFrom, adapterTo)
} }
@ -117,30 +114,7 @@ class QueueAdapter(
*/ */
fun removeItem(adapterIndex: Int) { fun removeItem(adapterIndex: Int) {
data.removeAt(adapterIndex) data.removeAt(adapterIndex)
notifyItemRemoved(adapterIndex)
/*
* If the data from the next queue is now entirely empty [Signified by a header at the
* end, remove the next queue header as notify as such.
*
* If the user queue is empty [Signified by there being two headers at the beginning with
* nothing in between], then remove the user queue header and notify as such.
*
* Otherwise just remove the item as usual.
*/
if (data[data.lastIndex] is Header) {
logD("Queue is empty, removing header")
val lastIndex = data.lastIndex
data.removeAt(lastIndex)
notifyItemRangeRemoved(lastIndex, 2)
} else if (data.lastIndex >= 1 && data[0] is ActionHeader && data[1] is Header) {
logD("User queue is empty, removing header")
data.removeAt(0)
notifyItemRangeRemoved(0, 2)
} else {
notifyItemRemoved(adapterIndex)
}
} }
/** /**

View file

@ -44,14 +44,9 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
override fun getMovementFlags( override fun getMovementFlags(
recyclerView: RecyclerView, recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder viewHolder: RecyclerView.ViewHolder
): Int { ): Int =
// Only allow dragging/swiping with the queue item ViewHolder, not the headers. makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN) or
return if (viewHolder is QueueAdapter.QueueSongViewHolder) { makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START)
makeFlag(
ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN
) or makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START)
} else 0
}
override fun interpolateOutOfBoundsScroll( override fun interpolateOutOfBoundsScroll(
recyclerView: RecyclerView, recyclerView: RecyclerView,
@ -152,15 +147,18 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
viewHolder: RecyclerView.ViewHolder, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder target: RecyclerView.ViewHolder
): Boolean { ): Boolean {
return playbackModel.moveQueueDataItems( val from = viewHolder.bindingAdapterPosition
viewHolder.bindingAdapterPosition, val to = target.bindingAdapterPosition
target.bindingAdapterPosition,
queueAdapter return playbackModel.moveQueueDataItems(from, to) {
) queueAdapter.moveItems(from, to)
}
} }
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition, queueAdapter) playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition) {
queueAdapter.removeItem(viewHolder.bindingAdapterPosition)
}
} }
/** /**

View file

@ -30,8 +30,7 @@ import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
/** /**
* A [Fragment] that contains both the user queue and the next queue, with the ability to * A [Fragment] that shows the queue and enables editing as well.
* edit them as well.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class QueueFragment : Fragment() { class QueueFragment : Fragment() {
@ -68,7 +67,7 @@ class QueueFragment : Fragment() {
// --- VIEWMODEL SETUP ---- // --- VIEWMODEL SETUP ----
playbackModel.displayQueue.observe(viewLifecycleOwner) { queue -> playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { queue ->
if (queue.isEmpty()) { if (queue.isEmpty()) {
findNavController().navigateUp() findNavController().navigateUp()
return@observe return@observe

View file

@ -44,7 +44,10 @@ class PlaybackStateDatabase(context: Context) :
createTable(db, TABLE_NAME_QUEUE) createTable(db, TABLE_NAME_QUEUE)
} }
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = nuke(db)
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = nuke(db)
private fun nuke(db: SQLiteDatabase) {
db.apply { db.apply {
execSQL("DROP TABLE IF EXISTS $TABLE_NAME_STATE") execSQL("DROP TABLE IF EXISTS $TABLE_NAME_STATE")
execSQL("DROP TABLE IF EXISTS $TABLE_NAME_QUEUE") execSQL("DROP TABLE IF EXISTS $TABLE_NAME_QUEUE")
@ -53,10 +56,6 @@ class PlaybackStateDatabase(context: Context) :
} }
} }
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
onUpgrade(db, newVersion, oldVersion)
}
// --- DATABASE CONSTRUCTION FUNCTIONS --- // --- DATABASE CONSTRUCTION FUNCTIONS ---
/** /**
@ -86,8 +85,7 @@ class PlaybackStateDatabase(context: Context) :
.append("${StateColumns.COLUMN_QUEUE_INDEX} INTEGER NOT NULL,") .append("${StateColumns.COLUMN_QUEUE_INDEX} INTEGER NOT NULL,")
.append("${StateColumns.COLUMN_PLAYBACK_MODE} INTEGER NOT NULL,") .append("${StateColumns.COLUMN_PLAYBACK_MODE} INTEGER NOT NULL,")
.append("${StateColumns.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,") .append("${StateColumns.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,")
.append("${StateColumns.COLUMN_LOOP_MODE} INTEGER NOT NULL,") .append("${StateColumns.COLUMN_LOOP_MODE} INTEGER NOT NULL)")
.append("${StateColumns.COLUMN_IS_IN_USER_QUEUE} BOOLEAN NOT NULL)")
return command return command
} }
@ -98,8 +96,7 @@ class PlaybackStateDatabase(context: Context) :
private fun constructQueueTable(command: StringBuilder): StringBuilder { private fun constructQueueTable(command: StringBuilder): StringBuilder {
command.append("${QueueColumns.ID} LONG PRIMARY KEY,") command.append("${QueueColumns.ID} LONG PRIMARY KEY,")
.append("${QueueColumns.SONG_HASH} INTEGER NOT NULL,") .append("${QueueColumns.SONG_HASH} INTEGER NOT NULL,")
.append("${QueueColumns.ALBUM_HASH} INTEGER NOT NULL,") .append("${QueueColumns.ALBUM_HASH} INTEGER NOT NULL)")
.append("${QueueColumns.IS_USER_QUEUE} BOOLEAN NOT NULL)")
return command return command
} }
@ -126,7 +123,6 @@ class PlaybackStateDatabase(context: Context) :
put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.toInt()) put(StateColumns.COLUMN_PLAYBACK_MODE, state.playbackMode.toInt())
put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling) put(StateColumns.COLUMN_IS_SHUFFLING, state.isShuffling)
put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.toInt()) put(StateColumns.COLUMN_LOOP_MODE, state.loopMode.toInt())
put(StateColumns.COLUMN_IS_IN_USER_QUEUE, state.isInUserQueue)
} }
insert(TABLE_NAME_STATE, null, stateData) insert(TABLE_NAME_STATE, null, stateData)
@ -155,9 +151,6 @@ class PlaybackStateDatabase(context: Context) :
val modeIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_PLAYBACK_MODE) val modeIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_PLAYBACK_MODE)
val shuffleIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_IS_SHUFFLING) val shuffleIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_IS_SHUFFLING)
val loopModeIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_LOOP_MODE) val loopModeIndex = cursor.getColumnIndexOrThrow(StateColumns.COLUMN_LOOP_MODE)
val isInUserQueueIndex = cursor.getColumnIndexOrThrow(
StateColumns.COLUMN_IS_IN_USER_QUEUE
)
cursor.moveToFirst() cursor.moveToFirst()
@ -184,7 +177,6 @@ class PlaybackStateDatabase(context: Context) :
playbackMode = mode, playbackMode = mode,
isShuffling = cursor.getInt(shuffleIndex) == 1, isShuffling = cursor.getInt(shuffleIndex) == 1,
loopMode = LoopMode.fromInt(cursor.getInt(loopModeIndex)) ?: LoopMode.NONE, loopMode = LoopMode.fromInt(cursor.getInt(loopModeIndex)) ?: LoopMode.NONE,
isInUserQueue = cursor.getInt(isInUserQueueIndex) == 1
) )
} }
@ -192,9 +184,9 @@ class PlaybackStateDatabase(context: Context) :
} }
/** /**
* Write a [SavedQueue] to the database. * Write a queue to the database.
*/ */
fun writeQueue(queue: SavedQueue) { fun writeQueue(queue: MutableList<Song>) {
assertBackgroundThread() assertBackgroundThread()
val database = writableDatabase val database = writableDatabase
@ -205,12 +197,11 @@ class PlaybackStateDatabase(context: Context) :
logD("Wiped queue db.") logD("Wiped queue db.")
writeQueueBatch(queue.user, true, 0) writeQueueBatch(queue, queue.size)
writeQueueBatch(queue.queue, false, queue.user.size)
} }
private fun writeQueueBatch(queue: List<Song>, isUserQueue: Boolean, idStart: Int) { private fun writeQueueBatch(queue: List<Song>, idStart: Int) {
logD("Beginning queue write [start: $idStart, userQueue: $isUserQueue]") logD("Beginning queue write [start: $idStart]")
val database = writableDatabase val database = writableDatabase
var position = 0 var position = 0
@ -227,7 +218,6 @@ class PlaybackStateDatabase(context: Context) :
put(QueueColumns.ID, idStart + i) put(QueueColumns.ID, idStart + i)
put(QueueColumns.SONG_HASH, song.hash) put(QueueColumns.SONG_HASH, song.hash)
put(QueueColumns.ALBUM_HASH, song.album.hash) put(QueueColumns.ALBUM_HASH, song.album.hash)
put(QueueColumns.IS_USER_QUEUE, isUserQueue)
} }
insert(TABLE_NAME_QUEUE, null, itemData) insert(TABLE_NAME_QUEUE, null, itemData)
@ -243,29 +233,25 @@ class PlaybackStateDatabase(context: Context) :
} }
/** /**
* Read a [SavedQueue] from this database. * Read a list of queue items from this database.
* @param musicStore Required to transform database songs into actual song instances * @param musicStore Required to transform database songs into actual song instances
*/ */
fun readQueue(musicStore: MusicStore): SavedQueue { fun readQueue(musicStore: MusicStore): MutableList<Song> {
assertBackgroundThread() assertBackgroundThread()
val queue = SavedQueue(mutableListOf(), mutableListOf()) val queue = mutableListOf<Song>()
readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor -> readableDatabase.queryAll(TABLE_NAME_QUEUE) { cursor ->
if (cursor.count == 0) return@queryAll if (cursor.count == 0) return@queryAll
val songIndex = cursor.getColumnIndexOrThrow(QueueColumns.SONG_HASH) val songIndex = cursor.getColumnIndexOrThrow(QueueColumns.SONG_HASH)
val albumIndex = cursor.getColumnIndexOrThrow(QueueColumns.ALBUM_HASH) val albumIndex = cursor.getColumnIndexOrThrow(QueueColumns.ALBUM_HASH)
val isUserQueueIndex = cursor.getColumnIndexOrThrow(QueueColumns.IS_USER_QUEUE)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
musicStore.findSongFast(cursor.getLong(songIndex), cursor.getLong(albumIndex))?.let { song -> musicStore.findSongFast(cursor.getLong(songIndex), cursor.getLong(albumIndex))
if (cursor.getInt(isUserQueueIndex) == 1) { ?.let { song ->
queue.user.add(song) queue.add(song)
} else {
queue.queue.add(song)
} }
}
} }
} }
@ -280,11 +266,8 @@ class PlaybackStateDatabase(context: Context) :
val playbackMode: PlaybackMode, val playbackMode: PlaybackMode,
val isShuffling: Boolean, val isShuffling: Boolean,
val loopMode: LoopMode, val loopMode: LoopMode,
val isInUserQueue: Boolean
) )
data class SavedQueue(val user: MutableList<Song>, val queue: MutableList<Song>)
private object StateColumns { private object StateColumns {
const val COLUMN_ID = "id" const val COLUMN_ID = "id"
const val COLUMN_SONG_HASH = "song" const val COLUMN_SONG_HASH = "song"
@ -294,19 +277,17 @@ class PlaybackStateDatabase(context: Context) :
const val COLUMN_PLAYBACK_MODE = "playback_mode" const val COLUMN_PLAYBACK_MODE = "playback_mode"
const val COLUMN_IS_SHUFFLING = "is_shuffling" const val COLUMN_IS_SHUFFLING = "is_shuffling"
const val COLUMN_LOOP_MODE = "loop_mode" const val COLUMN_LOOP_MODE = "loop_mode"
const val COLUMN_IS_IN_USER_QUEUE = "is_in_user_queue"
} }
private object QueueColumns { private object QueueColumns {
const val ID = "id" const val ID = "id"
const val SONG_HASH = "song" const val SONG_HASH = "song"
const val ALBUM_HASH = "album" const val ALBUM_HASH = "album"
const val IS_USER_QUEUE = "is_user_queue"
} }
companion object { companion object {
const val DB_NAME = "auxio_state_database.db" const val DB_NAME = "auxio_state_database.db"
const val DB_VERSION = 5 const val DB_VERSION = 6
const val TABLE_NAME_STATE = "playback_state_table" const val TABLE_NAME_STATE = "playback_state_table"
const val TABLE_NAME_QUEUE = "queue_table" const val TABLE_NAME_QUEUE = "queue_table"

View file

@ -30,6 +30,8 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
import kotlin.math.max
import kotlin.math.min
/** /**
* Master class (and possible god object) for the playback state. * Master class (and possible god object) for the playback state.
@ -65,11 +67,6 @@ class PlaybackStateManager private constructor() {
field = value field = value
callbacks.forEach { it.onQueueUpdate(value) } callbacks.forEach { it.onQueueUpdate(value) }
} }
private var mUserQueue = mutableListOf<Song>()
set(value) {
field = value
callbacks.forEach { it.onUserQueueUpdate(value) }
}
private var mIndex = 0 private var mIndex = 0
set(value) { set(value) {
field = value field = value
@ -98,11 +95,7 @@ class PlaybackStateManager private constructor() {
field = value field = value
callbacks.forEach { it.onLoopUpdate(value) } callbacks.forEach { it.onLoopUpdate(value) }
} }
private var mIsInUserQueue = false
set(value) {
field = value
callbacks.forEach { it.onInUserQueueUpdate(value) }
}
private var mIsRestored = false private var mIsRestored = false
private var mHasPlayed = false private var mHasPlayed = false
@ -114,8 +107,6 @@ class PlaybackStateManager private constructor() {
val position: Long get() = mPosition val position: Long get() = mPosition
/** The current queue determined by [parent] and [playbackMode] */ /** The current queue determined by [parent] and [playbackMode] */
val queue: List<Song> get() = mQueue val queue: List<Song> get() = mQueue
/** The queue created by the user. */
val userQueue: List<Song> get() = mUserQueue
/** The current index of the queue */ /** The current index of the queue */
val index: Int get() = mIndex val index: Int get() = mIndex
/** The current [PlaybackMode] */ /** The current [PlaybackMode] */
@ -250,8 +241,6 @@ class PlaybackStateManager private constructor() {
* Update the playback to a new [song], doing all the required logic. * Update the playback to a new [song], doing all the required logic.
*/ */
private fun updatePlayback(song: Song, shouldPlay: Boolean = true) { private fun updatePlayback(song: Song, shouldPlay: Boolean = true) {
mIsInUserQueue = false
mSong = song mSong = song
mPosition = 0 mPosition = 0
@ -264,29 +253,17 @@ class PlaybackStateManager private constructor() {
* Go to the next song, along with doing all the checks that entails. * Go to the next song, along with doing all the checks that entails.
*/ */
fun next() { fun next() {
// If there's anything in the user queue, go to the first song in there instead // Increment the index, if it cannot be incremented any further, then
// of incrementing the index. // loop and pause/resume playback depending on the setting
if (mUserQueue.isNotEmpty()) { if (mIndex < mQueue.lastIndex) {
updatePlayback(mUserQueue[0]) mIndex = mIndex.inc()
mUserQueue.removeAt(0) updatePlayback(mQueue[mIndex])
// Mark that the playback state is currently in the user queue, for later.
mIsInUserQueue = true
forceUserQueueUpdate()
} else { } else {
// Increment the index, if it cannot be incremented any further, then mIndex = 0
// loop and pause/resume playback depending on the setting updatePlayback(mQueue[mIndex], shouldPlay = mLoopMode == LoopMode.ALL)
if (mIndex < mQueue.lastIndex) {
mIndex = mIndex.inc()
updatePlayback(mQueue[mIndex])
} else {
mIndex = 0
updatePlayback(mQueue[mIndex], shouldPlay = mLoopMode == LoopMode.ALL)
}
forceQueueUpdate()
} }
forceQueueUpdate()
} }
/** /**
@ -297,9 +274,8 @@ class PlaybackStateManager private constructor() {
if (settingsManager.rewindWithPrev && mPosition >= REWIND_THRESHOLD) { if (settingsManager.rewindWithPrev && mPosition >= REWIND_THRESHOLD) {
rewind() rewind()
} else { } else {
// Only decrement the index if there's a song to move back to AND if we are not exiting // Only decrement the index if there's a song to move back to
// the user queue. if (mIndex > 0) {
if (mIndex > 0 && !mIsInUserQueue) {
mIndex = mIndex.dec() mIndex = mIndex.dec()
} }
@ -348,63 +324,35 @@ class PlaybackStateManager private constructor() {
} }
/** /**
* Add a [song] to the user queue. * Add a [song] to the top of the queue.
*/ */
fun addToUserQueue(song: Song) { fun playNext(song: Song) {
mUserQueue.add(song) mQueue.add(min(mIndex + 1, max(mQueue.lastIndex, 0)), song)
forceQueueUpdate()
forceUserQueueUpdate()
} }
/** /**
* Add a list of [songs] to the user queue. * Add a list of [songs] to the top of the queue.
*/ */
fun addToUserQueue(songs: List<Song>) { fun playNext(songs: List<Song>) {
mUserQueue.addAll(songs) mQueue.addAll(min(mIndex + 1, max(mQueue.lastIndex, 0)), songs)
forceQueueUpdate()
forceUserQueueUpdate()
} }
/** /**
* Remove a USER queue item at [index]. Will ignore invalid indexes. * Add a [song] to the end of the queue.
*/ */
fun removeUserQueueItem(index: Int) { fun addToQueue(song: Song) {
logD("Removing item ${mUserQueue[index].name}.") mQueue.add(song)
forceQueueUpdate()
if (index > mUserQueue.size || index < 0) {
logE("Index is out of bounds, did not remove user queue item.")
return
}
mUserQueue.removeAt(index)
forceUserQueueUpdate()
} }
/** /**
* Move a USER queue item at [from] to a position at [to]. Will ignore invalid indexes. * Add a list of [songs] to the end of the queue.
*/ */
fun moveUserQueueItems(from: Int, to: Int) { fun addToQueue(songs: List<Song>) {
if (from > mUserQueue.size || from < 0 || to > mUserQueue.size || to < 0) { mQueue.addAll(songs)
logE("Indices were out of bounds, did not move user queue item") forceQueueUpdate()
return
}
val item = mUserQueue.removeAt(from)
mUserQueue.add(to, item)
forceUserQueueUpdate()
}
/**
* Clear the user queue. Forces a user queue update.
*/
fun clearUserQueue() {
mUserQueue.clear()
forceUserQueueUpdate()
} }
/** /**
@ -414,36 +362,28 @@ class PlaybackStateManager private constructor() {
mQueue = mQueue mQueue = mQueue
} }
/**
* Force any callbacks to recieve a user queue update.
*/
private fun forceUserQueueUpdate() {
mUserQueue = mUserQueue
}
// --- SHUFFLE FUNCTIONS --- // --- SHUFFLE FUNCTIONS ---
/** /**
* Set whether this instance is [shuffled]. Updates the queue accordingly * Set whether this instance is [shuffled]. Updates the queue accordingly.
* @param keepSong Whether the current song should be kept as the queue is shuffled/unshuffled * @param keepSong Whether the current song should be kept as the queue is shuffled/unshuffled
*/ */
fun setShuffling(shuffled: Boolean, keepSong: Boolean) { fun setShuffling(shuffled: Boolean, keepSong: Boolean) {
mIsShuffling = shuffled mIsShuffling = shuffled
if (mIsShuffling) { if (mIsShuffling) {
genShuffle(keepSong, mIsInUserQueue) genShuffle(keepSong)
} else { } else {
resetShuffle(keepSong, mIsInUserQueue) resetShuffle(keepSong)
} }
} }
/** /**
* Generate a new shuffled queue. * Generate a new shuffled queue.
* @param keepSong Whether the current song should be kept as the queue is shuffled * @param keepSong Whether the current song should be kept as the queue is shuffled
* @param useLastSong Whether to use the last song in the queue instead of the current one
*/ */
private fun genShuffle(keepSong: Boolean, useLastSong: Boolean) { private fun genShuffle(keepSong: Boolean) {
val lastSong = if (useLastSong) mQueue[0] else mSong val lastSong = mSong
logD("Shuffling queue") logD("Shuffling queue")
@ -464,11 +404,10 @@ class PlaybackStateManager private constructor() {
/** /**
* Reset the queue to its normal, ordered state. * Reset the queue to its normal, ordered state.
* @param keepSong Whether the current song should be kept as the queue is unshuffled * @param keepSong Whether the current song should be kept as the queue is unshuffled
* @param useLastSong Whether to use the previous song for the index calculations.
*/ */
private fun resetShuffle(keepSong: Boolean, useLastSong: Boolean) { private fun resetShuffle(keepSong: Boolean) {
val musicStore = MusicStore.maybeGetInstance() ?: return val musicStore = MusicStore.maybeGetInstance() ?: return
val lastSong = if (useLastSong) mQueue[mIndex] else mSong val lastSong = mSong
mQueue = when (mPlaybackMode) { mQueue = when (mPlaybackMode) {
PlaybackMode.ALL_SONGS -> PlaybackMode.ALL_SONGS ->
@ -585,11 +524,12 @@ class PlaybackStateManager private constructor() {
database.writeState( database.writeState(
PlaybackStateDatabase.SavedState( PlaybackStateDatabase.SavedState(
mSong, mPosition, mParent, mIndex, mSong, mPosition, mParent, mIndex,
mPlaybackMode, mIsShuffling, mLoopMode, mIsInUserQueue mPlaybackMode, mIsShuffling, mLoopMode,
) )
) )
database.writeQueue(PlaybackStateDatabase.SavedQueue(mUserQueue, mQueue)) // TODO: Re-add state saving
database.writeQueue(mQueue)
this@PlaybackStateManager.logD( this@PlaybackStateManager.logD(
"Save finished in ${System.currentTimeMillis() - start}ms" "Save finished in ${System.currentTimeMillis() - start}ms"
@ -608,7 +548,7 @@ class PlaybackStateManager private constructor() {
val start: Long val start: Long
val playbackState: PlaybackStateDatabase.SavedState? val playbackState: PlaybackStateDatabase.SavedState?
val queue: PlaybackStateDatabase.SavedQueue val queue: MutableList<Song>
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
start = System.currentTimeMillis() start = System.currentTimeMillis()
@ -622,7 +562,7 @@ class PlaybackStateManager private constructor() {
// Get off the IO coroutine since it will cause LiveData updates to throw an exception // Get off the IO coroutine since it will cause LiveData updates to throw an exception
if (playbackState != null) { if (playbackState != null) {
logD("Found playback state $playbackState with queue size ${queue.user.size + queue.queue.size}") logD("Found playback state $playbackState")
unpackFromPlaybackState(playbackState) unpackFromPlaybackState(playbackState)
unpackQueue(queue) unpackQueue(queue)
@ -649,30 +589,21 @@ class PlaybackStateManager private constructor() {
mSong = playbackState.song mSong = playbackState.song
mLoopMode = playbackState.loopMode mLoopMode = playbackState.loopMode
mIsShuffling = playbackState.isShuffling mIsShuffling = playbackState.isShuffling
mIsInUserQueue = playbackState.isInUserQueue
seekTo(playbackState.position) seekTo(playbackState.position)
} }
/** private fun unpackQueue(queue: MutableList<Song>) {
* Unpack a list of queue items into a queue & user queue. mQueue = queue
*/
private fun unpackQueue(queue: PlaybackStateDatabase.SavedQueue) {
mUserQueue = queue.user
mQueue = queue.queue
// When done, get a more accurate index to prevent issues with queue songs that were saved // Sanity check: Ensure that the
// to the db but are now deleted when the restore occurred. mSong?.let { song ->
// Not done if in user queue because that could result in a bad index being created. while (mQueue.getOrNull(mIndex) != song) {
if (!mIsInUserQueue) { mIndex--
mSong?.let { song ->
val index = mQueue.indexOf(song)
mIndex = if (index != -1) index else mIndex
} }
} }
forceQueueUpdate() forceQueueUpdate()
forceUserQueueUpdate()
} }
/** /**
@ -702,14 +633,12 @@ class PlaybackStateManager private constructor() {
fun onParentUpdate(parent: MusicParent?) {} fun onParentUpdate(parent: MusicParent?) {}
fun onPositionUpdate(position: Long) {} fun onPositionUpdate(position: Long) {}
fun onQueueUpdate(queue: List<Song>) {} fun onQueueUpdate(queue: List<Song>) {}
fun onUserQueueUpdate(userQueue: List<Song>) {}
fun onModeUpdate(mode: PlaybackMode) {} fun onModeUpdate(mode: PlaybackMode) {}
fun onIndexUpdate(index: Int) {} fun onIndexUpdate(index: Int) {}
fun onPlayingUpdate(isPlaying: Boolean) {} fun onPlayingUpdate(isPlaying: Boolean) {}
fun onShuffleUpdate(isShuffling: Boolean) {} fun onShuffleUpdate(isShuffling: Boolean) {}
fun onLoopUpdate(loopMode: LoopMode) {} fun onLoopUpdate(loopMode: LoopMode) {}
fun onSeek(position: Long) {} fun onSeek(position: Long) {}
fun onInUserQueueUpdate(isInUserQueue: Boolean) {}
} }
companion object { companion object {

View file

@ -145,7 +145,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
} }
} }
SettingsManager.KEY_SHOW_COVERS, SettingsManager.KEY_QUALITY_COVERS, -> { SettingsManager.KEY_SHOW_COVERS, SettingsManager.KEY_QUALITY_COVERS -> {
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
Coil.imageLoader(requireContext()).apply { Coil.imageLoader(requireContext()).apply {
this.memoryCache?.clear() this.memoryCache?.clear()

View file

@ -144,15 +144,31 @@ class ActionMenu(
} }
} }
R.id.action_queue_add -> { R.id.action_play_next -> {
when (data) { when (data) {
is Song -> { is Song -> {
playbackModel.addToUserQueue(data) playbackModel.playNext(data)
context.showToast(R.string.lbl_queue_added) context.showToast(R.string.lbl_queue_added)
} }
is Album -> { is Album -> {
playbackModel.addToUserQueue(data) playbackModel.playNext(data)
context.showToast(R.string.lbl_queue_added)
}
else -> {}
}
}
R.id.action_queue_add -> {
when (data) {
is Song -> {
playbackModel.addToQueue(data)
context.showToast(R.string.lbl_queue_added)
}
is Album -> {
playbackModel.addToQueue(data)
context.showToast(R.string.lbl_queue_added) context.showToast(R.string.lbl_queue_added)
} }

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
This is part of a stopgap animation used by QueueFragment until it's fully integrated
into PlaybackLayout.
-->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fromAlpha="1"
android:interpolator="@android:interpolator/decelerate_quint"
android:toAlpha="0" />

View file

@ -6,6 +6,9 @@
<item <item
android:id="@+id/action_shuffle" android:id="@+id/action_shuffle"
android:title="@string/lbl_shuffle" /> android:title="@string/lbl_shuffle" />
<item
android:id="@+id/action_play_next"
android:title="@string/lbl_play_next" />
<item <item
android:id="@+id/action_queue_add" android:id="@+id/action_queue_add"
android:title="@string/lbl_queue_add" /> android:title="@string/lbl_queue_add" />

View file

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:app="http://schemas.android.com/apk/res-auto"> <item
android:id="@+id/action_play_next"
android:title="@string/lbl_play_next" />
<item <item
android:id="@+id/action_queue_add" android:id="@+id/action_queue_add"
android:title="@string/lbl_queue_add" android:title="@string/lbl_queue_add" />
app:showAsAction="never" />
</menu> </menu>

View file

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_play_next"
android:title="@string/lbl_play_next" />
<item <item
android:id="@+id/action_queue_add" android:id="@+id/action_queue_add"
android:title="@string/lbl_queue_add" /> android:title="@string/lbl_queue_add" />

View file

@ -9,8 +9,10 @@
android:id="@+id/action_shuffle" android:id="@+id/action_shuffle"
android:title="@string/lbl_shuffle" android:title="@string/lbl_shuffle"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/action_play_next"
android:title="@string/lbl_play_next" />
<item <item
android:id="@+id/action_queue_add" android:id="@+id/action_queue_add"
android:title="@string/lbl_queue_add" android:title="@string/lbl_queue_add" />
app:showAsAction="never" />
</menu> </menu>

View file

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_play_next"
android:title="@string/lbl_play_next" />
<item <item
android:id="@+id/action_queue_add" android:id="@+id/action_queue_add"
android:title="@string/lbl_queue_add" /> android:title="@string/lbl_queue_add" />

View file

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_play_next"
android:title="@string/lbl_play_next" />
<item <item
android:id="@+id/action_queue_add" android:id="@+id/action_queue_add"
android:title="@string/lbl_queue_add" /> android:title="@string/lbl_queue_add" />

View file

@ -30,9 +30,9 @@
<string name="lbl_play_genre">"Přehrát z žánru"</string> <string name="lbl_play_genre">"Přehrát z žánru"</string>
<string name="lbl_playback">"Nyní hraje"</string> <string name="lbl_playback">"Nyní hraje"</string>
<string name="lbl_queue">"Fronta"</string> <string name="lbl_queue">"Fronta"</string>
<string name="lbl_play_next">Další skladba</string>
<string name="lbl_queue_add">"Přidat do fronty"</string> <string name="lbl_queue_add">"Přidat do fronty"</string>
<string name="lbl_queue_added">"Přidáno do fronty"</string> <string name="lbl_queue_added">"Přidáno do fronty"</string>
<string name="lbl_next_user_queue">"Další ve frontě"</string>
<string name="lbl_go_artist">"Jít na umělce"</string> <string name="lbl_go_artist">"Jít na umělce"</string>
<string name="lbl_go_album">"Jít na album"</string> <string name="lbl_go_album">"Jít na album"</string>
<string name="lbl_state_saved">"Stav uložen"</string> <string name="lbl_state_saved">"Stav uložen"</string>
@ -100,7 +100,6 @@
<string name="desc_skip_prev">"Přeskočit na poslední skladbu"</string> <string name="desc_skip_prev">"Přeskočit na poslední skladbu"</string>
<string name="desc_change_loop">"Změnit režim opakování"</string> <string name="desc_change_loop">"Změnit režim opakování"</string>
<string name="desc_shuffle">"Zapnout nebo vypnout náhodné"</string> <string name="desc_shuffle">"Zapnout nebo vypnout náhodné"</string>
<string name="desc_clear_user_queue">"Vymazat frontu"</string>
<string name="desc_queue_handle">"Přesunout skladbu ve frontě"</string> <string name="desc_queue_handle">"Přesunout skladbu ve frontě"</string>
<string name="desc_clear_search">"Vymazat vyhledávání"</string> <string name="desc_clear_search">"Vymazat vyhledávání"</string>
<string name="desc_blacklist_delete">"Vymazat vyloučený adresář"</string> <string name="desc_blacklist_delete">"Vymazat vyloučený adresář"</string>
@ -136,7 +135,6 @@
<string name="clr_grey">"Šedá"</string> <string name="clr_grey">"Šedá"</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<string name="fmt_next_from">"Další z: %s"</string>
<string name="fmt_songs_loaded">"Načtené skladby: %d"</string> <string name="fmt_songs_loaded">"Načtené skladby: %d"</string>
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">
<item quantity="one">"%d skladba"</item> <item quantity="one">"%d skladba"</item>

View file

@ -29,9 +29,9 @@
<string name="lbl_playback">Aktuelle Wiedergabe</string> <string name="lbl_playback">Aktuelle Wiedergabe</string>
<string name="lbl_queue">Warteschlange</string> <string name="lbl_queue">Warteschlange</string>
<string name="lbl_play_next">Spiele als nächstes</string>
<string name="lbl_queue_add">Zur Warteschlange hinzufügen</string> <string name="lbl_queue_add">Zur Warteschlange hinzufügen</string>
<string name="lbl_queue_added">Der Warteschlange hinzugefügt</string> <string name="lbl_queue_added">Der Warteschlange hinzugefügt</string>
<string name="lbl_next_user_queue">Nächstes in Warteschlange</string>
<string name="lbl_go_artist">Zum Künstler gehen</string> <string name="lbl_go_artist">Zum Künstler gehen</string>
<string name="lbl_go_album">Zum Album gehen</string> <string name="lbl_go_album">Zum Album gehen</string>
@ -106,7 +106,6 @@
<string name="desc_change_loop">Art der Wiederholung ändern</string> <string name="desc_change_loop">Art der Wiederholung ändern</string>
<string name="desc_clear_search">Suchanfrage löschen</string> <string name="desc_clear_search">Suchanfrage löschen</string>
<string name="desc_clear_user_queue">Warteschlange leeren</string>
<string name="desc_auxio_icon">Auxio-Icon</string> <string name="desc_auxio_icon">Auxio-Icon</string>
<string name="desc_album_cover">Albumcover für %s</string> <string name="desc_album_cover">Albumcover für %s</string>
@ -136,7 +135,6 @@
<string name="clr_grey">Grau</string> <string name="clr_grey">Grau</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<string name="fmt_next_from">Nächsten von: %s</string>
<string name="fmt_songs_loaded">Geladene Lieder: %d</string> <string name="fmt_songs_loaded">Geladene Lieder: %d</string>
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">

View file

@ -21,9 +21,9 @@
<string name="lbl_playback">Παίζει τώρα</string> <string name="lbl_playback">Παίζει τώρα</string>
<string name="lbl_queue">Ουρά αναπαραγωγής</string> <string name="lbl_queue">Ουρά αναπαραγωγής</string>
<string name="lbl_play_next">Επόμενο</string>
<string name="lbl_queue_add">Προσθήκη στην ουρά αναπ/γής</string> <string name="lbl_queue_add">Προσθήκη στην ουρά αναπ/γής</string>
<string name="lbl_queue_added">Προστέθηκε ένας τίτλος στην ουρά αναπαραγωγής</string> <string name="lbl_queue_added">Προστέθηκε ένας τίτλος στην ουρά αναπαραγωγής</string>
<string name="lbl_next_user_queue">Επόμενο</string>
<string name="lbl_go_artist">Πήγαινε στον καλλιτέχνη</string> <string name="lbl_go_artist">Πήγαινε στον καλλιτέχνη</string>
<string name="lbl_go_album">Πήγαινε στο άλμπουμ</string> <string name="lbl_go_album">Πήγαινε στο άλμπουμ</string>
@ -55,8 +55,6 @@
<string name="desc_play_pause">Αναπαραγωγή/Παύση</string> <string name="desc_play_pause">Αναπαραγωγή/Παύση</string>
<string name="desc_clear_user_queue">Εκκαθάριση ουράς αναπαραγωγής</string>
<!-- Hint Namespace | EditText Hints --> <!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">Αναζήτηση στη βιβλιοθήκη…</string> <string name="hint_search_library">Αναζήτηση στη βιβλιοθήκη…</string>

View file

@ -30,9 +30,9 @@
<string name="lbl_playback">Reproducción actual</string> <string name="lbl_playback">Reproducción actual</string>
<string name="lbl_queue">Cola</string> <string name="lbl_queue">Cola</string>
<string name="lbl_play_next">Siguiente</string>
<string name="lbl_queue_add">Agregar a la cola</string> <string name="lbl_queue_add">Agregar a la cola</string>
<string name="lbl_queue_added">Agregada a la cola</string> <string name="lbl_queue_added">Agregada a la cola</string>
<string name="lbl_next_user_queue">A continuación…</string>
<string name="lbl_go_artist">Ir al artista</string> <string name="lbl_go_artist">Ir al artista</string>
<string name="lbl_go_album">Ir al álbum</string> <string name="lbl_go_album">Ir al álbum</string>
@ -108,7 +108,6 @@
<string name="desc_skip_prev">Saltar a la última canción</string> <string name="desc_skip_prev">Saltar a la última canción</string>
<string name="desc_change_loop">Cambiar el modo de repetición</string> <string name="desc_change_loop">Cambiar el modo de repetición</string>
<string name="desc_clear_user_queue">Limpiar cola</string>
<string name="desc_clear_search">Borrar historial de búsqueda</string> <string name="desc_clear_search">Borrar historial de búsqueda</string>
<string name="desc_blacklist_delete">Eliminar directorio excluido</string> <string name="desc_blacklist_delete">Eliminar directorio excluido</string>
@ -140,7 +139,6 @@
<string name="clr_grey">Gris</string> <string name="clr_grey">Gris</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<string name="fmt_next_from">Siguiente de: %s</string>
<string name="fmt_songs_loaded">Canciones encontradas: %d</string> <string name="fmt_songs_loaded">Canciones encontradas: %d</string>
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">Lecture en cours</string> <string name="lbl_playback">Lecture en cours</string>
<string name="lbl_queue">File d\'attente</string> <string name="lbl_queue">File d\'attente</string>
<string name="lbl_play_next">Jouer ensuite</string>
<string name="lbl_queue_add">Ajouter à la file d\'attente</string> <string name="lbl_queue_add">Ajouter à la file d\'attente</string>
<string name="lbl_queue_added">Ajouté à la file d\'attente</string> <string name="lbl_queue_added">Ajouté à la file d\'attente</string>
<string name="lbl_next_user_queue">Suivant</string>
<string name="lbl_go_album">Aller à l\'album</string> <string name="lbl_go_album">Aller à l\'album</string>
<string name="lbl_go_artist">Aller à l\'artiste</string> <string name="lbl_go_artist">Aller à l\'artiste</string>
@ -62,8 +62,6 @@
<string name="desc_play_pause">Lecture/Pause</string> <string name="desc_play_pause">Lecture/Pause</string>
<string name="desc_clear_user_queue">Effacer la file d\'attente</string>
<!-- Hint Namespace | EditText Hints --> <!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">Recherche dans votre bibliothèque…</string> <string name="hint_search_library">Recherche dans votre bibliothèque…</string>

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">Most Játszott</string> <string name="lbl_playback">Most Játszott</string>
<string name="lbl_queue">Lejátszási sor</string> <string name="lbl_queue">Lejátszási sor</string>
<string name="lbl_play_next">Lejátszás következőnek</string>
<string name="lbl_queue_add">Lejátszás sorhoz adás</string> <string name="lbl_queue_add">Lejátszás sorhoz adás</string>
<string name="lbl_queue_added">Sorbaállítva</string> <string name="lbl_queue_added">Sorbaállítva</string>
<string name="lbl_next_user_queue">Lejátszási sor</string>
<string name="lbl_go_artist">Ugrás az előadóhoz</string> <string name="lbl_go_artist">Ugrás az előadóhoz</string>
<string name="lbl_go_album">Ugrás az albumhoz</string> <string name="lbl_go_album">Ugrás az albumhoz</string>
@ -64,8 +64,6 @@
<string name="desc_play_pause">Lejátszás/Szünet</string> <string name="desc_play_pause">Lejátszás/Szünet</string>
<string name="desc_clear_user_queue">Lejátszási sor</string>
<!-- Color Label namespace | Accent names --> <!-- Color Label namespace | Accent names -->
<string name="clr_red">Piros</string> <string name="clr_red">Piros</string>
<string name="clr_pink">Rózsaszínű</string> <string name="clr_pink">Rózsaszínű</string>

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">Sedang Diputar</string> <string name="lbl_playback">Sedang Diputar</string>
<string name="lbl_queue">Antrean</string> <string name="lbl_queue">Antrean</string>
<string name="lbl_play_next">Putar selanjutnya</string>
<string name="lbl_queue_add">Tambahkan ke antrean</string> <string name="lbl_queue_add">Tambahkan ke antrean</string>
<string name="lbl_queue_added">Ditambahkan ke antrean</string> <string name="lbl_queue_added">Ditambahkan ke antrean</string>
<string name="lbl_next_user_queue">Berikutnya</string>
<string name="lbl_go_artist">Pergi ke artis</string> <string name="lbl_go_artist">Pergi ke artis</string>
<string name="lbl_go_album">Pergi ke album</string> <string name="lbl_go_album">Pergi ke album</string>
@ -64,8 +64,6 @@
<string name="desc_play_pause">Putar/Jeda</string> <string name="desc_play_pause">Putar/Jeda</string>
<string name="desc_clear_user_queue">Kosongkan antrean</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">Schermata di riproduzione</string> <string name="lbl_playback">Schermata di riproduzione</string>
<string name="lbl_queue">Coda</string> <string name="lbl_queue">Coda</string>
<string name="lbl_play_next">Riproduci successivo</string>
<string name="lbl_queue_add">Aggiungi alla coda</string> <string name="lbl_queue_add">Aggiungi alla coda</string>
<string name="lbl_queue_added">Aggiunta alla coda</string> <string name="lbl_queue_added">Aggiunta alla coda</string>
<string name="lbl_next_user_queue">A seguire</string>
<string name="lbl_go_artist">Vai all\'artista</string> <string name="lbl_go_artist">Vai all\'artista</string>
<string name="lbl_go_album">Vai all\'album</string> <string name="lbl_go_album">Vai all\'album</string>
@ -63,8 +63,6 @@
<string name="desc_play_pause">Play/Pausa</string> <string name="desc_play_pause">Play/Pausa</string>
<string name="desc_clear_user_queue">Svuota coda</string>
<!-- Hint Namespace | EditText Hints --> <!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">Cerca nella tua libreria…</string> <string name="hint_search_library">Cerca nella tua libreria…</string>

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">지금 재생 중</string> <string name="lbl_playback">지금 재생 중</string>
<string name="lbl_queue">대기열</string> <string name="lbl_queue">대기열</string>
<string name="lbl_play_next">다음 곡으로 재생</string>
<string name="lbl_queue_add">대기열에 추가</string> <string name="lbl_queue_add">대기열에 추가</string>
<string name="lbl_queue_added">가 대기열에 추가되었습니다</string> <string name="lbl_queue_added">가 대기열에 추가되었습니다</string>
<string name="lbl_next_user_queue">다음 곡</string>
<string name="lbl_go_artist">아티스트로 가기</string> <string name="lbl_go_artist">아티스트로 가기</string>
<string name="lbl_go_album">앨범으로 가기</string> <string name="lbl_go_album">앨범으로 가기</string>
@ -60,8 +60,6 @@
<string name="desc_play_pause">재생/일시 정지</string> <string name="desc_play_pause">재생/일시 정지</string>
<string name="desc_clear_user_queue">재생 대기열 비우기</string>
<!-- Hint Namespace | EditText Hints --> <!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">저장소 검색…</string> <string name="hint_search_library">저장소 검색…</string>

View file

@ -30,9 +30,9 @@
<string name="lbl_playback">Afspeelscherm</string> <string name="lbl_playback">Afspeelscherm</string>
<string name="lbl_queue">Wachtrij</string> <string name="lbl_queue">Wachtrij</string>
<string name="lbl_play_next">Afspelen als volgende</string>
<string name="lbl_queue_add">Toevoegen aan wachtrij</string> <string name="lbl_queue_add">Toevoegen aan wachtrij</string>
<string name="lbl_queue_added">Toegevoegd aan de wachtrij</string> <string name="lbl_queue_added">Toegevoegd aan de wachtrij</string>
<string name="lbl_next_user_queue">Als volgende</string>
<string name="lbl_go_artist">Ga naar artiest</string> <string name="lbl_go_artist">Ga naar artiest</string>
<string name="lbl_go_album">Ga naar album</string> <string name="lbl_go_album">Ga naar album</string>
@ -106,7 +106,6 @@
<string name="desc_skip_prev">Naar het laatste nummer gaan</string> <string name="desc_skip_prev">Naar het laatste nummer gaan</string>
<string name="desc_change_loop">Herhaalfunctie wijzigen</string> <string name="desc_change_loop">Herhaalfunctie wijzigen</string>
<string name="desc_clear_user_queue">Wachtrij wissen</string>
<string name="desc_clear_search">Zoekopdracht wissen</string> <string name="desc_clear_search">Zoekopdracht wissen</string>
<string name="desc_blacklist_delete">Verwijder uitgesloten map</string> <string name="desc_blacklist_delete">Verwijder uitgesloten map</string>
@ -138,7 +137,6 @@
<string name="clr_grey">Grijis</string> <string name="clr_grey">Grijis</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<string name="fmt_next_from">Volgende van: %s</string>
<string name="fmt_songs_loaded">Nummers geladen: %d</string> <string name="fmt_songs_loaded">Nummers geladen: %d</string>
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">Obecnie Grane</string> <string name="lbl_playback">Obecnie Grane</string>
<string name="lbl_queue">Kolejka</string> <string name="lbl_queue">Kolejka</string>
<string name="lbl_play_next">Odtwarzaj następny</string>
<string name="lbl_queue_add">Dodaj do kolejki</string> <string name="lbl_queue_add">Dodaj do kolejki</string>
<string name="lbl_queue_added">Dodany do kolejki</string> <string name="lbl_queue_added">Dodany do kolejki</string>
<string name="lbl_next_user_queue">Za chwilę zagra</string>
<string name="lbl_go_artist">Przejdź do wykonawcy</string> <string name="lbl_go_artist">Przejdź do wykonawcy</string>
<string name="lbl_go_album">Przejdź do albumu</string> <string name="lbl_go_album">Przejdź do albumu</string>
@ -61,8 +61,6 @@
<string name="desc_play_pause">Odtwarzanie/Pauza</string> <string name="desc_play_pause">Odtwarzanie/Pauza</string>
<string name="desc_clear_user_queue">Wyczyść kolejkę</string>
<!-- Hint Namespace | EditText Hints --> <!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">Szukaj w bibliotece…</string> <string name="hint_search_library">Szukaj w bibliotece…</string>

View file

@ -21,9 +21,9 @@
<string name="lbl_playback">Tocando agora</string> <string name="lbl_playback">Tocando agora</string>
<string name="lbl_queue">Fila</string> <string name="lbl_queue">Fila</string>
<string name="lbl_play_next">Reproduzir próxima</string>
<string name="lbl_queue_add">Adicionar à fila</string> <string name="lbl_queue_add">Adicionar à fila</string>
<string name="lbl_queue_added">Adicionada à fila</string> <string name="lbl_queue_added">Adicionada à fila</string>
<string name="lbl_next_user_queue">Próximo</string>
<string name="lbl_go_artist">Ir para o artista</string> <string name="lbl_go_artist">Ir para o artista</string>
<string name="lbl_go_album">Ir para o álbum</string> <string name="lbl_go_album">Ir para o álbum</string>
@ -62,8 +62,6 @@
<string name="desc_play_pause">Reproduzir/Pausar</string> <string name="desc_play_pause">Reproduzir/Pausar</string>
<string name="desc_clear_user_queue">Limpar fila</string>
<!-- Hint Namespace | EditText Hints --> <!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">Procurar na biblioteca…</string> <string name="hint_search_library">Procurar na biblioteca…</string>

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">A reproduzir</string> <string name="lbl_playback">A reproduzir</string>
<string name="lbl_queue">Fila</string> <string name="lbl_queue">Fila</string>
<string name="lbl_play_next">Reproduzir a próxima</string>
<string name="lbl_queue_add">Adicionar à fila</string> <string name="lbl_queue_add">Adicionar à fila</string>
<string name="lbl_queue_added">Adicionada à fila</string> <string name="lbl_queue_added">Adicionada à fila</string>
<string name="lbl_next_user_queue">A seguir</string>
<string name="lbl_go_artist">Ir para o artista</string> <string name="lbl_go_artist">Ir para o artista</string>
<string name="lbl_go_album">Ir para o álbum</string> <string name="lbl_go_album">Ir para o álbum</string>
@ -63,8 +63,6 @@
<string name="desc_play_pause">Reproduzir/Pausar</string> <string name="desc_play_pause">Reproduzir/Pausar</string>
<string name="desc_clear_user_queue">Limpar fila</string>
<!-- Hint Namespace | EditText Hints --> <!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">Procurar na biblioteca…</string> <string name="hint_search_library">Procurar na biblioteca…</string>

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">Redare Acum</string> <string name="lbl_playback">Redare Acum</string>
<string name="lbl_queue">Fila de așteptare</string> <string name="lbl_queue">Fila de așteptare</string>
<string name="lbl_play_next">Redaţi următoarea</string>
<string name="lbl_queue_add">Adăugați la lista de așteptare</string> <string name="lbl_queue_add">Adăugați la lista de așteptare</string>
<string name="lbl_queue_added">A fost adăugat la liste de așteptare</string> <string name="lbl_queue_added">A fost adăugat la liste de așteptare</string>
<string name="lbl_next_user_queue">Urmează</string>
<string name="lbl_go_artist">Accesaţi artistul</string> <string name="lbl_go_artist">Accesaţi artistul</string>
<string name="lbl_go_album">Accesaţi albumul</string> <string name="lbl_go_album">Accesaţi albumul</string>
@ -65,8 +65,6 @@
<string name="desc_play_pause">Redă/Pauză</string> <string name="desc_play_pause">Redă/Pauză</string>
<string name="desc_clear_user_queue">Golește lista de redare</string>
<!-- Color Label namespace | Accent names --> <!-- Color Label namespace | Accent names -->
<string name="clr_red">Roșu</string> <string name="clr_red">Roșu</string>
<string name="clr_pink">Roz</string> <string name="clr_pink">Roz</string>

View file

@ -36,9 +36,9 @@
<string name="lbl_play_genre">Воспроизвести из жанра</string> <string name="lbl_play_genre">Воспроизвести из жанра</string>
<string name="lbl_queue">Очередь</string> <string name="lbl_queue">Очередь</string>
<string name="lbl_play_next">Воспроизвести далее</string>
<string name="lbl_queue_add">Добавить в очередь</string> <string name="lbl_queue_add">Добавить в очередь</string>
<string name="lbl_queue_added">Добавлено в очередь</string> <string name="lbl_queue_added">Добавлено в очередь</string>
<string name="lbl_next_user_queue">Далее в очереди</string>
<string name="lbl_go_artist">Перейти к исполнителю</string> <string name="lbl_go_artist">Перейти к исполнителю</string>
<string name="lbl_go_album">Перейти к альбому</string> <string name="lbl_go_album">Перейти к альбому</string>
@ -121,7 +121,6 @@
<string name="desc_shuffle">Включить или выключить перемешивание</string> <string name="desc_shuffle">Включить или выключить перемешивание</string>
<string name="desc_shuffle_all">Перемешать все композиции</string> <string name="desc_shuffle_all">Перемешать все композиции</string>
<string name="desc_clear_user_queue">Очистить очередь</string>
<string name="desc_clear_queue_item">Удалить композицию из очереди</string> <string name="desc_clear_queue_item">Удалить композицию из очереди</string>
<string name="desc_queue_handle">Переместить композицию в очереди</string> <string name="desc_queue_handle">Переместить композицию в очереди</string>
<string name="desc_tab_handle">Переместить вкладку</string> <string name="desc_tab_handle">Переместить вкладку</string>
@ -161,7 +160,6 @@
<string name="clr_grey">Серый</string> <string name="clr_grey">Серый</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<string name="fmt_next_from">Далее из: %s</string>
<string name="fmt_songs_loaded">Всего композиций: %d</string> <string name="fmt_songs_loaded">Всего композиций: %d</string>
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">Şuan çalınan</string> <string name="lbl_playback">Şuan çalınan</string>
<string name="lbl_queue">Kuyruk</string> <string name="lbl_queue">Kuyruk</string>
<string name="lbl_play_next">Sonraki şarkı</string>
<string name="lbl_queue_add">Kuyruğa ekle</string> <string name="lbl_queue_add">Kuyruğa ekle</string>
<string name="lbl_queue_added">Kuyruğa eklendi</string> <string name="lbl_queue_added">Kuyruğa eklendi</string>
<string name="lbl_next_user_queue">Sırada olanlar</string>
<string name="lbl_go_artist">Sanatçıya git</string> <string name="lbl_go_artist">Sanatçıya git</string>
<string name="lbl_go_album">Albüme git</string> <string name="lbl_go_album">Albüme git</string>
@ -63,8 +63,6 @@
<string name="desc_play_pause">Başlat/Durdur</string> <string name="desc_play_pause">Başlat/Durdur</string>
<string name="desc_clear_user_queue">Temizle Kuyruk</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">

View file

@ -21,9 +21,9 @@
<string name="lbl_playback">Відтворюється</string> <string name="lbl_playback">Відтворюється</string>
<string name="lbl_queue">Черга</string> <string name="lbl_queue">Черга</string>
<string name="lbl_play_next">Відтворити наступною</string>
<string name="lbl_queue_add">Додати в чергу</string> <string name="lbl_queue_add">Додати в чергу</string>
<string name="lbl_queue_added">Додана в чергу</string> <string name="lbl_queue_added">Додана в чергу</string>
<string name="lbl_next_user_queue">До наступного</string>
<string name="lbl_go_artist">Перейти до виконавця</string> <string name="lbl_go_artist">Перейти до виконавця</string>
<string name="lbl_go_album">Перейти до альбому</string> <string name="lbl_go_album">Перейти до альбому</string>
@ -59,8 +59,6 @@
<string name="desc_play_pause">Відтворити/Зупинити</string> <string name="desc_play_pause">Відтворити/Зупинити</string>
<string name="desc_clear_user_queue">Очистити черга</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<string name="fmt_songs_loaded">Пісні завантажено: %d</string> <string name="fmt_songs_loaded">Пісні завантажено: %d</string>

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">正在播放界面</string> <string name="lbl_playback">正在播放界面</string>
<string name="lbl_queue">播放队列</string> <string name="lbl_queue">播放队列</string>
<string name="lbl_play_next">作为下一首播放</string>
<string name="lbl_queue_add">加入播放队列</string> <string name="lbl_queue_add">加入播放队列</string>
<string name="lbl_queue_added">此歌曲已加入播放队列</string> <string name="lbl_queue_added">此歌曲已加入播放队列</string>
<string name="lbl_next_user_queue">即将播放</string>
<string name="lbl_go_artist">查看艺术家</string> <string name="lbl_go_artist">查看艺术家</string>
<string name="lbl_go_album">查看专辑</string> <string name="lbl_go_album">查看专辑</string>
@ -56,8 +56,6 @@
<string name="desc_play_pause">播放/暂停</string> <string name="desc_play_pause">播放/暂停</string>
<string name="desc_clear_user_queue">清空播放队列</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">
<item quantity="other">%d 歌曲</item> <item quantity="other">%d 歌曲</item>

View file

@ -22,9 +22,9 @@
<string name="lbl_playback">播放面板</string> <string name="lbl_playback">播放面板</string>
<string name="lbl_queue">隊列</string> <string name="lbl_queue">隊列</string>
<string name="lbl_play_next">下一首播放</string>
<string name="lbl_queue_add">添加到隊列</string> <string name="lbl_queue_add">添加到隊列</string>
<string name="lbl_queue_added">已加入隊列</string> <string name="lbl_queue_added">已加入隊列</string>
<string name="lbl_next_user_queue">播放佇列:即將播放</string>
<string name="lbl_go_artist">前往該歌手頁面</string> <string name="lbl_go_artist">前往該歌手頁面</string>
<string name="lbl_go_album">專輯</string> <string name="lbl_go_album">專輯</string>
@ -61,8 +61,6 @@
<string name="desc_play_pause">播放/暫停</string> <string name="desc_play_pause">播放/暫停</string>
<string name="desc_clear_user_queue">清空播放佇列</string>
<!-- Hint Namespace | EditText Hints --> <!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">搜尋音樂庫…</string> <string name="hint_search_library">搜尋音樂庫…</string>

View file

@ -6,26 +6,6 @@
<attr name="entryValues" format="reference" /> <attr name="entryValues" format="reference" />
</declare-styleable> </declare-styleable>
<declare-styleable name="SlidingUpPanelLayout">
<attr name="umanoPanelHeight" format="dimension" />
<attr name="umanoShadowHeight" format="dimension" />
<attr name="umanoParallaxOffset" format="dimension" />
<attr name="umanoFadeColor" format="color" />
<attr name="umanoFlingVelocity" format="integer" />
<attr name="umanoDragView" format="reference" />
<attr name="umanoScrollableView" format="reference" />
<attr name="umanoOverlay" format="boolean"/>
<attr name="umanoClipPanel" format="boolean"/>
<attr name="umanoAnchorPoint" format="float" />
<attr name="umanoInitialState" format="enum">
<enum name="expanded" value="0" />
<enum name="collapsed" value="1" />
<enum name="anchored" value="2" />
<enum name="hidden" value="3" />
</attr>
<attr name="umanoScrollInterpolator" format="reference" />
</declare-styleable>
<string-array name="entires_theme"> <string-array name="entires_theme">
<item>@string/set_theme_auto</item> <item>@string/set_theme_auto</item>
<item>@string/set_theme_day</item> <item>@string/set_theme_day</item>

View file

@ -36,9 +36,9 @@
<string name="lbl_play_genre">Play from genre</string> <string name="lbl_play_genre">Play from genre</string>
<string name="lbl_queue">Queue</string> <string name="lbl_queue">Queue</string>
<string name="lbl_play_next">Play next</string>
<string name="lbl_queue_add">Add to queue</string> <string name="lbl_queue_add">Add to queue</string>
<string name="lbl_queue_added">Added to queue</string> <string name="lbl_queue_added">Added to queue</string>
<string name="lbl_next_user_queue">Next in Queue</string>
<string name="lbl_go_artist">Go to artist</string> <string name="lbl_go_artist">Go to artist</string>
<string name="lbl_go_album">Go to album</string> <string name="lbl_go_album">Go to album</string>
@ -122,7 +122,6 @@
<string name="desc_shuffle">Turn shuffle on or off</string> <string name="desc_shuffle">Turn shuffle on or off</string>
<string name="desc_shuffle_all">Shuffle all songs</string> <string name="desc_shuffle_all">Shuffle all songs</string>
<string name="desc_clear_user_queue">Clear queue</string>
<string name="desc_clear_queue_item">Remove this queue song</string> <string name="desc_clear_queue_item">Remove this queue song</string>
<string name="desc_queue_handle">Move this queue song</string> <string name="desc_queue_handle">Move this queue song</string>
<string name="desc_tab_handle">Move this tab</string> <string name="desc_tab_handle">Move this tab</string>
@ -162,7 +161,6 @@
<string name="clr_grey">Grey</string> <string name="clr_grey">Grey</string>
<!-- Format Namespace | Value formatting/plurals --> <!-- Format Namespace | Value formatting/plurals -->
<string name="fmt_next_from">Next From: %s</string>
<string name="fmt_songs_loaded">Songs loaded: %d</string> <string name="fmt_songs_loaded">Songs loaded: %d</string>
<plurals name="fmt_song_count"> <plurals name="fmt_song_count">

View file

@ -171,11 +171,6 @@
<item name="shapeAppearanceOverlay">@style/ShapeAppearance.Auxio.FloatingActionButton.PlayPause</item> <item name="shapeAppearanceOverlay">@style/ShapeAppearance.Auxio.FloatingActionButton.PlayPause</item>
</style> </style>
<style name="ThemeOverlay.Auxio.FloatingActionButton.PlayPause" parent="">
<item name="colorContainer">?attr/colorSecondary</item>
<item name="colorOnContainer">?attr/colorOnSecondary</item>
</style>
<style name="ShapeAppearance.Auxio.FloatingActionButton.PlayPause" parent=""> <style name="ShapeAppearance.Auxio.FloatingActionButton.PlayPause" parent="">
<item name="cornerSize">50%</item> <item name="cornerSize">50%</item>
</style> </style>

View file

@ -27,4 +27,3 @@ Feel free to fork Auxio to add your own feature set however.
- Recently added list [#18] - Recently added list [#18]
- Lyrics [#19] - Lyrics [#19]
- Tag editing [#33] - Tag editing [#33]
- Specialized queue adding (ex. Play Next) [#44]