Add user queue
Add a user-generated queue, currently it isnt played from.
This commit is contained in:
parent
bc7950e7af
commit
2be7d34601
36 changed files with 464 additions and 148 deletions
|
@ -25,7 +25,7 @@ class DetailAlbumAdapter(
|
|||
// Generic ViewHolder for a detail album
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemArtistAlbumBinding,
|
||||
) : BaseViewHolder<Album>(binding, doOnClick) {
|
||||
) : BaseViewHolder<Album>(binding, doOnClick, null) {
|
||||
|
||||
override fun onBind(data: Album) {
|
||||
binding.album = data
|
||||
|
|
|
@ -25,7 +25,7 @@ class DetailArtistAdapter(
|
|||
// Generic ViewHolder for an album
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemGenreArtistBinding
|
||||
) : BaseViewHolder<Artist>(binding, doOnClick) {
|
||||
) : BaseViewHolder<Artist>(binding, doOnClick, null) {
|
||||
|
||||
override fun onBind(data: Artist) {
|
||||
binding.artist = data
|
||||
|
|
|
@ -24,7 +24,7 @@ class DetailSongAdapter(
|
|||
// Generic ViewHolder for a song
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemAlbumSongBinding,
|
||||
) : BaseViewHolder<Song>(binding, doOnClick) {
|
||||
) : BaseViewHolder<Song>(binding, doOnClick, null) {
|
||||
|
||||
override fun onBind(data: Song) {
|
||||
binding.song = data
|
||||
|
|
|
@ -50,9 +50,12 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
|
|||
navToItem(it)
|
||||
}
|
||||
|
||||
val searchAdapter = SearchAdapter {
|
||||
navToItem(it)
|
||||
}
|
||||
val searchAdapter = SearchAdapter(
|
||||
{
|
||||
navToItem(it)
|
||||
},
|
||||
{ data, view -> }
|
||||
)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.oxycblt.auxio.library.adapters
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -17,7 +18,8 @@ import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder
|
|||
import org.oxycblt.auxio.recycler.viewholders.SongViewHolder
|
||||
|
||||
class SearchAdapter(
|
||||
private val doOnClick: (data: BaseModel) -> Unit
|
||||
private val doOnClick: (data: BaseModel) -> Unit,
|
||||
private val doOnLongClick: (data: BaseModel, view: View) -> Unit
|
||||
) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback<BaseModel>()) {
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
|
@ -35,7 +37,11 @@ class SearchAdapter(
|
|||
GenreViewHolder.ITEM_TYPE -> GenreViewHolder.from(parent.context, doOnClick)
|
||||
ArtistViewHolder.ITEM_TYPE -> ArtistViewHolder.from(parent.context, doOnClick)
|
||||
AlbumViewHolder.ITEM_TYPE -> AlbumViewHolder.from(parent.context, doOnClick)
|
||||
SongViewHolder.ITEM_TYPE -> SongViewHolder.from(parent.context, doOnClick)
|
||||
SongViewHolder.ITEM_TYPE -> SongViewHolder.from(
|
||||
parent.context,
|
||||
doOnClick,
|
||||
doOnLongClick
|
||||
)
|
||||
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
|
||||
|
||||
else -> HeaderViewHolder.from(parent.context)
|
||||
|
|
|
@ -81,6 +81,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
|
||||
// Make marquee scroll work
|
||||
binding.playbackSong.isSelected = true
|
||||
|
||||
binding.playbackSeekBar.setOnSeekBarChangeListener(this)
|
||||
|
||||
// --- VIEWMODEL SETUP --
|
||||
|
@ -178,7 +179,7 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
|
||||
playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) {
|
||||
// Disable the option to open the queue if there's nothing in it.
|
||||
if (it.isEmpty()) {
|
||||
if (it.isEmpty() && playbackModel.userQueue.value!!.isEmpty()) {
|
||||
queueMenuItem.isEnabled = false
|
||||
queueMenuItem.icon = iconQueueInactive
|
||||
} else {
|
||||
|
@ -195,6 +196,16 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
}
|
||||
}
|
||||
|
||||
playbackModel.userQueue.observe(viewLifecycleOwner) {
|
||||
if (it.isEmpty() && playbackModel.queue.value!!.isEmpty()) {
|
||||
queueMenuItem.isEnabled = false
|
||||
queueMenuItem.icon = iconQueueInactive
|
||||
} else {
|
||||
queueMenuItem.isEnabled = true
|
||||
queueMenuItem.icon = iconQueueActive
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(this::class.simpleName, "Fragment Created.")
|
||||
|
||||
return binding.root
|
||||
|
|
|
@ -33,6 +33,9 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
private val mQueue = MutableLiveData(mutableListOf<Song>())
|
||||
val queue: LiveData<MutableList<Song>> get() = mQueue
|
||||
|
||||
private val mUserQueue = MutableLiveData(mutableListOf<Song>())
|
||||
val userQueue: LiveData<MutableList<Song>> get() = mUserQueue
|
||||
|
||||
private val mIndex = MutableLiveData(0)
|
||||
val index: LiveData<Int> get() = mIndex
|
||||
|
||||
|
@ -169,6 +172,18 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
playbackManager.moveQueueItems(from, to)
|
||||
}
|
||||
|
||||
fun addToUserQueue(song: Song) {
|
||||
playbackManager.addToUserQueue(song)
|
||||
}
|
||||
|
||||
fun moveUserQueueItems(from: Int, to: Int) {
|
||||
playbackManager.moveUserQueueItems(from, to)
|
||||
}
|
||||
|
||||
fun removeUserQueueItem(index: Int) {
|
||||
playbackManager.removeUserQueueItem(index)
|
||||
}
|
||||
|
||||
// --- STATUS FUNCTIONS ---
|
||||
|
||||
// Flip the playing status.
|
||||
|
@ -215,6 +230,10 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
mQueue.value = queue
|
||||
}
|
||||
|
||||
override fun onUserQueueUpdate(userQueue: MutableList<Song>) {
|
||||
mUserQueue.value = userQueue
|
||||
}
|
||||
|
||||
override fun onIndexUpdate(index: Int) {
|
||||
mIndex.value = index
|
||||
}
|
||||
|
@ -241,6 +260,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback {
|
|||
mSong.value = playbackManager.song
|
||||
mPosition.value = playbackManager.position / 1000
|
||||
mQueue.value = playbackManager.queue
|
||||
mUserQueue.value = playbackManager.userQueue
|
||||
mIndex.value = playbackManager.index
|
||||
mIsPlaying.value = playbackManager.isPlaying
|
||||
mIsShuffling.value = playbackManager.isShuffling
|
||||
|
|
|
@ -25,7 +25,7 @@ class QueueAdapter(
|
|||
// Generic ViewHolder for a queue item
|
||||
inner class ViewHolder(
|
||||
private val binding: ItemQueueSongBinding,
|
||||
) : BaseViewHolder<Song>(binding, null) {
|
||||
) : BaseViewHolder<Song>(binding, null, null) {
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onBind(data: Song) {
|
||||
|
|
|
@ -10,7 +10,8 @@ import kotlin.math.sign
|
|||
|
||||
// The drag callback used for the Queue RecyclerView.
|
||||
class QueueDragCallback(
|
||||
private val playbackModel: PlaybackViewModel
|
||||
private val playbackModel: PlaybackViewModel,
|
||||
private val isUserQueue: Boolean
|
||||
) : ItemTouchHelper.SimpleCallback(
|
||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
|
||||
ItemTouchHelper.START
|
||||
|
@ -24,7 +25,6 @@ class QueueDragCallback(
|
|||
): Int {
|
||||
// Fix to make QueueFragment scroll when an item is scrolled out of bounds.
|
||||
// Adapted from NewPipe: https://github.com/TeamNewPipe/NewPipe
|
||||
|
||||
val standardSpeed = super.interpolateOutOfBoundsScroll(
|
||||
recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll
|
||||
)
|
||||
|
@ -45,13 +45,21 @@ class QueueDragCallback(
|
|||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
playbackModel.moveQueueItems(viewHolder.adapterPosition, target.adapterPosition)
|
||||
if (isUserQueue) {
|
||||
playbackModel.moveUserQueueItems(viewHolder.adapterPosition, target.adapterPosition)
|
||||
} else {
|
||||
playbackModel.moveQueueItems(viewHolder.adapterPosition, target.adapterPosition)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
playbackModel.removeQueueItem(viewHolder.adapterPosition)
|
||||
if (isUserQueue) {
|
||||
playbackModel.removeUserQueueItem(viewHolder.adapterPosition)
|
||||
} else {
|
||||
playbackModel.removeQueueItem(viewHolder.adapterPosition)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -4,25 +4,17 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.FragmentQueueBinding
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.theme.accent
|
||||
import org.oxycblt.auxio.theme.applyDivider
|
||||
import org.oxycblt.auxio.theme.toColor
|
||||
|
||||
// TODO: Make this better
|
||||
class QueueFragment : BottomSheetDialogFragment() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
|
||||
override fun getTheme(): Int = R.style.Theme_BottomSheetFix
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -30,58 +22,24 @@ class QueueFragment : BottomSheetDialogFragment() {
|
|||
): View? {
|
||||
val binding = FragmentQueueBinding.inflate(inflater)
|
||||
|
||||
val helper = ItemTouchHelper(QueueDragCallback(playbackModel))
|
||||
val queueAdapter = QueueAdapter(helper)
|
||||
binding.queueViewpager.adapter = PagerAdapter()
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
binding.queueHeader.setTextColor(accent.first.toColor(requireContext()))
|
||||
binding.queueRecycler.apply {
|
||||
adapter = queueAdapter
|
||||
itemAnimator = DefaultItemAnimator()
|
||||
applyDivider()
|
||||
setHasFixedSize(true)
|
||||
|
||||
helper.attachToRecyclerView(this)
|
||||
}
|
||||
|
||||
// --- VIEWMODEL SETUP ---
|
||||
|
||||
playbackModel.mode.observe(viewLifecycleOwner) {
|
||||
if (it == PlaybackMode.ALL_SONGS) {
|
||||
binding.queueHeader.setText(R.string.label_next_songs)
|
||||
} else {
|
||||
binding.queueHeader.text = getString(
|
||||
R.string.format_next_from, playbackModel.parent.value!!.name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) {
|
||||
if (it.isEmpty()) {
|
||||
findNavController().navigateUp()
|
||||
|
||||
return@observe
|
||||
}
|
||||
|
||||
// If the first item is being moved, then scroll to the top position on completion
|
||||
// to prevent ListAdapter from scrolling uncontrollably.
|
||||
if (queueAdapter.currentList.isNotEmpty() && it[0].id != queueAdapter.currentList[0].id) {
|
||||
queueAdapter.submitList(it.toMutableList()) {
|
||||
// Make sure that the RecyclerView doesn't scroll to the top if the first item
|
||||
// changed, but is not visible.
|
||||
val firstItem = (binding.queueRecycler.layoutManager as LinearLayoutManager)
|
||||
.findFirstVisibleItemPosition()
|
||||
|
||||
if (firstItem == -1 || firstItem == 0) {
|
||||
binding.queueRecycler.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
queueAdapter.submitList(it.toMutableList())
|
||||
}
|
||||
// TODO: Add option for default queue screen
|
||||
if (playbackModel.userQueue.value!!.isEmpty()) {
|
||||
binding.queueViewpager.setCurrentItem(1, false)
|
||||
} else {
|
||||
binding.queueViewpager.setCurrentItem(0, false)
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private inner class PagerAdapter :
|
||||
FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) {
|
||||
override fun getItemCount(): Int = 2
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return QueueListFragment(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
package org.oxycblt.auxio.playback.queue
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.FragmentQueueListBinding
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.theme.applyDivider
|
||||
|
||||
class QueueListFragment(private val type: Int) : Fragment() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val binding = FragmentQueueListBinding.inflate(inflater)
|
||||
|
||||
// --- UI SETUP ---
|
||||
|
||||
binding.queueRecycler.apply {
|
||||
itemAnimator = DefaultItemAnimator()
|
||||
applyDivider()
|
||||
setHasFixedSize(true)
|
||||
}
|
||||
|
||||
// Continue setup with different values depending on the type
|
||||
when (type) {
|
||||
TYPE_NEXT_QUEUE -> setupForNextQueue(binding)
|
||||
TYPE_USER_QUEUE -> setupForUserQueue(binding)
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun setupForNextQueue(binding: FragmentQueueListBinding) {
|
||||
val helper = ItemTouchHelper(QueueDragCallback(playbackModel, false))
|
||||
val queueNextAdapter = QueueAdapter(helper)
|
||||
|
||||
binding.queueRecycler.apply {
|
||||
adapter = queueNextAdapter
|
||||
helper.attachToRecyclerView(this)
|
||||
}
|
||||
|
||||
playbackModel.mode.observe(viewLifecycleOwner) {
|
||||
if (it == PlaybackMode.ALL_SONGS) {
|
||||
binding.queueHeader.setText(R.string.label_next_songs)
|
||||
} else {
|
||||
binding.queueHeader.text = getString(
|
||||
R.string.format_next_from, playbackModel.parent.value!!.name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) {
|
||||
if (it.isEmpty()) {
|
||||
if (playbackModel.userQueue.value!!.isEmpty()) {
|
||||
findNavController().navigateUp()
|
||||
} else {
|
||||
binding.queueNothingIndicator.visibility = View.VISIBLE
|
||||
binding.queueRecycler.visibility = View.GONE
|
||||
}
|
||||
|
||||
return@observe
|
||||
}
|
||||
|
||||
binding.queueNothingIndicator.visibility = View.GONE
|
||||
binding.queueRecycler.visibility = View.VISIBLE
|
||||
|
||||
// If the first item is being moved, then scroll to the top position on completion
|
||||
// to prevent ListAdapter from scrolling uncontrollably.
|
||||
if (queueNextAdapter.currentList.isNotEmpty() &&
|
||||
it[0].id != queueNextAdapter.currentList[0].id
|
||||
) {
|
||||
queueNextAdapter.submitList(it.toMutableList()) {
|
||||
scrollRecyclerIfNeeded(binding)
|
||||
}
|
||||
} else {
|
||||
queueNextAdapter.submitList(it.toMutableList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupForUserQueue(binding: FragmentQueueListBinding) {
|
||||
val helper = ItemTouchHelper(QueueDragCallback(playbackModel, true))
|
||||
val userQueueAdapter = QueueAdapter(helper)
|
||||
|
||||
binding.queueHeader.setText(R.string.label_next_user_queue)
|
||||
|
||||
binding.queueRecycler.apply {
|
||||
adapter = userQueueAdapter
|
||||
helper.attachToRecyclerView(this)
|
||||
}
|
||||
|
||||
playbackModel.userQueue.observe(viewLifecycleOwner) {
|
||||
if (it.isEmpty()) {
|
||||
if (playbackModel.queue.value!!.isEmpty()) {
|
||||
findNavController().navigateUp()
|
||||
} else {
|
||||
binding.queueNothingIndicator.visibility = View.VISIBLE
|
||||
binding.queueRecycler.visibility = View.GONE
|
||||
}
|
||||
|
||||
return@observe
|
||||
}
|
||||
|
||||
binding.queueNothingIndicator.visibility = View.GONE
|
||||
binding.queueRecycler.visibility = View.VISIBLE
|
||||
|
||||
// If the first item is being moved, then scroll to the top position on completion
|
||||
// to prevent ListAdapter from scrolling uncontrollably.
|
||||
if (userQueueAdapter.currentList.isNotEmpty() &&
|
||||
it[0].id != userQueueAdapter.currentList[0].id
|
||||
) {
|
||||
userQueueAdapter.submitList(it.toMutableList()) {
|
||||
scrollRecyclerIfNeeded(binding)
|
||||
}
|
||||
} else {
|
||||
userQueueAdapter.submitList(it.toMutableList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollRecyclerIfNeeded(binding: FragmentQueueListBinding) {
|
||||
if ((binding.queueRecycler.layoutManager as LinearLayoutManager)
|
||||
.findFirstVisibleItemPosition() < 1
|
||||
) {
|
||||
binding.queueRecycler.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TYPE_USER_QUEUE = 0
|
||||
const val TYPE_NEXT_QUEUE = 1
|
||||
}
|
||||
}
|
|
@ -40,6 +40,12 @@ class PlaybackStateManager private constructor() {
|
|||
field = value
|
||||
callbacks.forEach { it.onQueueUpdate(value) }
|
||||
}
|
||||
private var mUserQueue = mutableListOf<Song>()
|
||||
set(value) {
|
||||
Log.d(this::class.simpleName, "retard.")
|
||||
field = value
|
||||
callbacks.forEach { it.onUserQueueUpdate(value) }
|
||||
}
|
||||
private var mIndex = 0
|
||||
set(value) {
|
||||
field = value
|
||||
|
@ -74,6 +80,7 @@ class PlaybackStateManager private constructor() {
|
|||
val parent: BaseModel? get() = mParent
|
||||
val position: Long get() = mPosition
|
||||
val queue: MutableList<Song> get() = mQueue
|
||||
val userQueue: MutableList<Song> get() = mUserQueue
|
||||
val index: Int get() = mIndex
|
||||
val mode: PlaybackMode get() = mMode
|
||||
val isPlaying: Boolean get() = mIsPlaying
|
||||
|
@ -252,10 +259,8 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
fun moveQueueItems(from: Int, to: Int) {
|
||||
try {
|
||||
val currentItem = mQueue[from]
|
||||
|
||||
mQueue.removeAt(from)
|
||||
mQueue.add(to, currentItem)
|
||||
val item = mQueue.removeAt(from)
|
||||
mQueue.add(to, item)
|
||||
} catch (exception: IndexOutOfBoundsException) {
|
||||
Log.e(this::class.simpleName, "Indices were out of bounds, did not move queue item")
|
||||
|
||||
|
@ -265,11 +270,48 @@ class PlaybackStateManager private constructor() {
|
|||
forceQueueUpdate()
|
||||
}
|
||||
|
||||
fun addToUserQueue(song: Song) {
|
||||
mUserQueue.add(song)
|
||||
|
||||
forceUserQueueUpdate()
|
||||
}
|
||||
|
||||
fun removeUserQueueItem(index: Int) {
|
||||
Log.d(this::class.simpleName, "Removing item ${mUserQueue[index].name}.")
|
||||
|
||||
if (index > mUserQueue.size || index < 0) {
|
||||
Log.e(this::class.simpleName, "Index is out of bounds, did not remove queue item.")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
mUserQueue.removeAt(index)
|
||||
|
||||
forceUserQueueUpdate()
|
||||
}
|
||||
|
||||
fun moveUserQueueItems(from: Int, to: Int) {
|
||||
try {
|
||||
val item = mUserQueue.removeAt(from)
|
||||
mUserQueue.add(to, item)
|
||||
} catch (exception: IndexOutOfBoundsException) {
|
||||
Log.e(this::class.simpleName, "Indices were out of bounds, did not move queue item")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
forceUserQueueUpdate()
|
||||
}
|
||||
|
||||
// Force any callbacks to update when the queue is changed.
|
||||
private fun forceQueueUpdate() {
|
||||
mQueue = mQueue
|
||||
}
|
||||
|
||||
private fun forceUserQueueUpdate() {
|
||||
mUserQueue = mUserQueue
|
||||
}
|
||||
|
||||
// --- SHUFFLE FUNCTIONS ---
|
||||
|
||||
fun shuffleAll() {
|
||||
|
@ -408,6 +450,7 @@ class PlaybackStateManager private constructor() {
|
|||
fun onParentUpdate(parent: BaseModel?) {}
|
||||
fun onPositionUpdate(position: Long) {}
|
||||
fun onQueueUpdate(queue: MutableList<Song>) {}
|
||||
fun onUserQueueUpdate(userQueue: MutableList<Song>) {}
|
||||
fun onModeUpdate(mode: PlaybackMode) {}
|
||||
fun onIndexUpdate(index: Int) {}
|
||||
fun onPlayingUpdate(isPlaying: Boolean) {}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.oxycblt.auxio.recycler.viewholders
|
||||
|
||||
import android.view.View
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.music.BaseModel
|
||||
|
@ -7,7 +8,8 @@ import org.oxycblt.auxio.music.BaseModel
|
|||
// ViewHolder abstraction that automates some of the things that are common for all ViewHolders.
|
||||
abstract class BaseViewHolder<T : BaseModel>(
|
||||
private val baseBinding: ViewDataBinding,
|
||||
private val doOnClick: ((data: T) -> Unit)?
|
||||
private val doOnClick: ((data: T) -> Unit)?,
|
||||
private val doOnLongClick: ((data: T, view: View) -> Unit)?
|
||||
) : RecyclerView.ViewHolder(baseBinding.root) {
|
||||
init {
|
||||
// Force the layout to *actually* be the screen width
|
||||
|
@ -23,6 +25,14 @@ abstract class BaseViewHolder<T : BaseModel>(
|
|||
}
|
||||
}
|
||||
|
||||
doOnLongClick?.let { onLongClick ->
|
||||
baseBinding.root.setOnLongClickListener {
|
||||
onLongClick(data, baseBinding.root)
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
onBind(data)
|
||||
|
||||
baseBinding.executePendingBindings()
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.oxycblt.auxio.recycler.viewholders
|
|||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import org.oxycblt.auxio.databinding.ItemAlbumBinding
|
||||
import org.oxycblt.auxio.databinding.ItemArtistBinding
|
||||
import org.oxycblt.auxio.databinding.ItemGenreBinding
|
||||
|
@ -19,7 +20,7 @@ import org.oxycblt.auxio.music.Song
|
|||
class GenreViewHolder private constructor(
|
||||
private val binding: ItemGenreBinding,
|
||||
doOnClick: (Genre) -> Unit
|
||||
) : BaseViewHolder<Genre>(binding, doOnClick) {
|
||||
) : BaseViewHolder<Genre>(binding, doOnClick, null) {
|
||||
|
||||
override fun onBind(data: Genre) {
|
||||
binding.genre = data
|
||||
|
@ -41,7 +42,7 @@ class GenreViewHolder private constructor(
|
|||
class ArtistViewHolder private constructor(
|
||||
private val binding: ItemArtistBinding,
|
||||
doOnClick: (Artist) -> Unit,
|
||||
) : BaseViewHolder<Artist>(binding, doOnClick) {
|
||||
) : BaseViewHolder<Artist>(binding, doOnClick, null) {
|
||||
|
||||
override fun onBind(data: Artist) {
|
||||
binding.artist = data
|
||||
|
@ -63,7 +64,7 @@ class ArtistViewHolder private constructor(
|
|||
class AlbumViewHolder private constructor(
|
||||
private val binding: ItemAlbumBinding,
|
||||
doOnClick: (data: Album) -> Unit
|
||||
) : BaseViewHolder<Album>(binding, doOnClick) {
|
||||
) : BaseViewHolder<Album>(binding, doOnClick, null) {
|
||||
|
||||
override fun onBind(data: Album) {
|
||||
binding.album = data
|
||||
|
@ -87,7 +88,8 @@ class AlbumViewHolder private constructor(
|
|||
class SongViewHolder private constructor(
|
||||
private val binding: ItemSongBinding,
|
||||
doOnClick: (data: Song) -> Unit,
|
||||
) : BaseViewHolder<Song>(binding, doOnClick) {
|
||||
doOnLongClick: (data: Song, view: View) -> Unit
|
||||
) : BaseViewHolder<Song>(binding, doOnClick, doOnLongClick) {
|
||||
|
||||
override fun onBind(data: Song) {
|
||||
binding.song = data
|
||||
|
@ -102,10 +104,11 @@ class SongViewHolder private constructor(
|
|||
fun from(
|
||||
context: Context,
|
||||
doOnClick: (data: Song) -> Unit,
|
||||
doOnLongClick: (data: Song, view: View) -> Unit
|
||||
): SongViewHolder {
|
||||
return SongViewHolder(
|
||||
ItemSongBinding.inflate(LayoutInflater.from(context)),
|
||||
doOnClick
|
||||
doOnClick, doOnLongClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +116,7 @@ class SongViewHolder private constructor(
|
|||
|
||||
class HeaderViewHolder(
|
||||
private val binding: ItemHeaderBinding
|
||||
) : BaseViewHolder<Header>(binding, null) {
|
||||
) : BaseViewHolder<Header>(binding, null, null) {
|
||||
|
||||
override fun onBind(data: Header) {
|
||||
binding.header = data
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.oxycblt.auxio.songs
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
@ -7,13 +8,14 @@ import org.oxycblt.auxio.recycler.viewholders.SongViewHolder
|
|||
|
||||
class SongAdapter(
|
||||
private val data: List<Song>,
|
||||
private val doOnClick: (data: Song) -> Unit
|
||||
private val doOnClick: (data: Song) -> Unit,
|
||||
private val doOnLongClick: (data: Song, view: View) -> Unit
|
||||
) : RecyclerView.Adapter<SongViewHolder>() {
|
||||
|
||||
override fun getItemCount(): Int = data.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder {
|
||||
return SongViewHolder.from(parent.context, doOnClick)
|
||||
return SongViewHolder.from(parent.context, doOnClick, doOnLongClick)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
|
||||
|
|
|
@ -5,11 +5,13 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.PopupMenu
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.FragmentSongsBinding
|
||||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.theme.applyDivider
|
||||
|
@ -39,9 +41,15 @@ class SongsFragment : Fragment() {
|
|||
}
|
||||
|
||||
binding.songRecycler.apply {
|
||||
adapter = SongAdapter(musicStore.songs) {
|
||||
playbackModel.playSong(it, PlaybackMode.ALL_SONGS)
|
||||
}
|
||||
adapter = SongAdapter(
|
||||
musicStore.songs,
|
||||
{
|
||||
playbackModel.playSong(it, PlaybackMode.ALL_SONGS)
|
||||
},
|
||||
{ data, view ->
|
||||
showActionMenuForSong(data, view)
|
||||
}
|
||||
)
|
||||
applyDivider()
|
||||
setHasFixedSize(true)
|
||||
}
|
||||
|
@ -50,4 +58,21 @@ class SongsFragment : Fragment() {
|
|||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun showActionMenuForSong(song: Song, view: View) {
|
||||
// TODO: Replace this with something nicer
|
||||
PopupMenu(requireContext(), view).apply {
|
||||
inflate(R.menu.menu_song_actions)
|
||||
setOnMenuItemClickListener {
|
||||
if (it.itemId == R.id.action_queue_add) {
|
||||
playbackModel.addToUserQueue(song)
|
||||
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,16 @@
|
|||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:type="linear"
|
||||
android:startX="60" android:startY="61.5"
|
||||
android:endX="64.75" android:endY="28.5">
|
||||
<item android:color="#2196f3" android:offset="0.0"/>
|
||||
<item android:color="#90caf9" android:offset="1.0"/>
|
||||
android:startX="60"
|
||||
android:startY="61.5"
|
||||
android:endX="64.75"
|
||||
android:endY="28.5">
|
||||
<item
|
||||
android:color="#2196f3"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#90caf9"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||
</vector>
|
|
@ -11,10 +11,16 @@
|
|||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:type="linear"
|
||||
android:startX="60" android:startY="61.5"
|
||||
android:endX="64.75" android:endY="28.5">
|
||||
<item android:color="#2196f3" android:offset="0.0"/>
|
||||
<item android:color="#90caf9" android:offset="1.0"/>
|
||||
android:startX="60"
|
||||
android:startY="61.5"
|
||||
android:endX="64.75"
|
||||
android:endY="28.5">
|
||||
<item
|
||||
android:color="#2196f3"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#90caf9"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z" />
|
||||
</vector>
|
|
@ -4,7 +4,7 @@
|
|||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#80ffffff"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
|
||||
<path
|
||||
android:fillColor="#80ffffff"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z" />
|
||||
</vector>
|
|
@ -5,7 +5,7 @@
|
|||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z" />
|
||||
</vector>
|
|
@ -5,7 +5,7 @@
|
|||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4zM13,15L13,9h-1l-2,1v1h1.5v4L13,15z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4zM13,15L13,9h-1l-2,1v1h1.5v4L13,15z" />
|
||||
</vector>
|
|
@ -5,7 +5,7 @@
|
|||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4zM13,15L13,9h-1l-2,1v1h1.5v4L13,15z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4zM13,15L13,9h-1l-2,1v1h1.5v4L13,15z" />
|
||||
</vector>
|
|
@ -5,6 +5,7 @@
|
|||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path android:fillColor="@android:color/white"
|
||||
android:pathData="M5.571 19.5h4.286v-15H5.571zm8.572-15v15h4.286v-15z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M5.571 19.5h4.286v-15H5.571zm8.572-15v15h4.286v-15z" />
|
||||
</vector>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path android:fillColor="@android:color/white"
|
||||
android:pathData="M5.078 4.089V12h13.844zm0 15.822V12h13.844z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M5.078 4.089V12h13.844zm0 15.822V12h13.844z" />
|
||||
</vector>
|
||||
|
|
11
app/src/main/res/drawable/ic_user_queue.xml
Normal file
11
app/src/main/res/drawable/ic_user_queue.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM19,11h-4v4h-2v-4L9,11L9,9h4L13,5h2v4h4v2z" />
|
||||
</vector>
|
|
@ -15,6 +15,7 @@
|
|||
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
|
||||
</data>
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/playback_layout"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -213,7 +214,7 @@
|
|||
app:layout_constraintBottom_toBottomOf="@+id/playback_play_pause"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/playback_play_pause"
|
||||
android:contentDescription="@string/description_loop"/>
|
||||
android:contentDescription="@string/description_loop" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
|
@ -2,28 +2,15 @@
|
|||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<LinearLayout
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:theme="@style/Theme.Base"
|
||||
android:background="@color/background">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/queue_header"
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/queue_viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/padding_medium"
|
||||
android:text="@string/label_queue"
|
||||
android:fontFamily="@font/inter_black"
|
||||
android:textAppearance="@style/TextAppearance.Toolbar.Header" />
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/queue_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/item_song"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</layout>
|
49
app/src/main/res/layout/fragment_queue_list.xml
Normal file
49
app/src/main/res/layout/fragment_queue_list.xml
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/queue_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:fontFamily="@font/inter_black"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:padding="@dimen/padding_medium"
|
||||
android:singleLine="true"
|
||||
android:text="@string/label_queue"
|
||||
android:textColor="?android:attr/colorPrimary"
|
||||
android:textAppearance="@style/TextAppearance.Toolbar.Header"
|
||||
app:layout_constraintEnd_toStartOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/queue_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintTop_toBottomOf="@+id/queue_header"
|
||||
tools:layout_editor_absoluteX="0dp"
|
||||
tools:listitem="@layout/item_song" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/queue_nothing_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:textSize="15sp"
|
||||
android:text="@string/label_empty_queue"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
7
app/src/main/res/menu/menu_song_actions.xml
Normal file
7
app/src/main/res/menu/menu_song_actions.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/action_queue_add"
|
||||
android:title="@string/label_queue_add"
|
||||
android:icon="@drawable/ic_user_queue" />
|
||||
</menu>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
|
@ -115,14 +115,18 @@
|
|||
android:id="@+id/playback_fragment"
|
||||
android:name="org.oxycblt.auxio.playback.PlaybackFragment"
|
||||
android:label="PlaybackFragment"
|
||||
tools:layout="@layout/fragment_playback" >
|
||||
tools:layout="@layout/fragment_playback">
|
||||
<action
|
||||
android:id="@+id/action_show_queue"
|
||||
app:destination="@id/queue_fragment" />
|
||||
app:destination="@id/queue_fragment"
|
||||
app:enterAnim="@anim/anim_nav_slide_up"
|
||||
app:exitAnim="@anim/anim_stationary"
|
||||
app:popEnterAnim="@anim/anim_stationary"
|
||||
app:popExitAnim="@anim/anim_nav_slide_down" />
|
||||
</fragment>
|
||||
<dialog
|
||||
android:id="@+id/queue_fragment"
|
||||
android:name="org.oxycblt.auxio.playback.queue.QueueFragment"
|
||||
android:label="QueueFragment"
|
||||
tools:layout="@layout/fragment_queue"/>
|
||||
tools:layout="@layout/fragment_queue" />
|
||||
</navigation>
|
|
@ -27,7 +27,9 @@
|
|||
<string name="label_play">Play</string>
|
||||
<string name="label_queue">Queue</string>
|
||||
<string name="label_queue_add">Add to queue</string>
|
||||
<string name="label_next_user_queue">Next in Queue</string>
|
||||
<string name="label_next_songs">Next from: All Songs</string>
|
||||
<string name="label_empty_queue">Nothing here.</string>
|
||||
<string name="label_notification_playback">Music Playback</string>
|
||||
<string name="label_service_playback">The music playback service for Auxio.</string>
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<item name="android:textCursorDrawable">@drawable/ui_cursor</item>
|
||||
<item name="android:fitsSystemWindows">true</item>
|
||||
|
||||
<item name="android:popupMenuStyle">@style/Widget.CustomPopup</item>
|
||||
<item name="colorControlNormal">@color/control_color</item>
|
||||
</style>
|
||||
|
||||
|
@ -41,6 +42,11 @@
|
|||
<item name="colorControlHighlight">@color/selection_color</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.CustomPopup" parent="Widget.AppCompat.PopupMenu">
|
||||
<item name="android:colorBackground">@color/background</item>
|
||||
<item name="android:popupBackground">@color/background</item>
|
||||
</style>
|
||||
|
||||
<!--
|
||||
Fix to get QueueFragment to not overlap the Status Bar or Navigation Bar
|
||||
https://stackoverflow.com/a/57790787/14143986
|
||||
|
|
Loading…
Reference in a new issue