From 67d10009d4c8aae16a184a2fdf996361a004cd3d Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Mon, 9 Nov 2020 15:39:13 -0700 Subject: [PATCH] Unify Queue Make QueueFragment contain both the user queue and the next queue, instead of having viewpager between the two. --- .../auxio/detail/AlbumDetailFragment.kt | 2 - .../oxycblt/auxio/library/LibraryFragment.kt | 12 +- .../oxycblt/auxio/library/LibraryViewModel.kt | 11 +- .../org/oxycblt/auxio/music/MusicUtils.kt | 16 -- .../oxycblt/auxio/playback/PlaybackService.kt | 10 +- .../auxio/playback/PlaybackViewModel.kt | 62 +++++--- .../auxio/playback/queue/QueueAdapter.kt | 42 ++++- .../auxio/playback/queue/QueueDragCallback.kt | 37 +++-- .../auxio/playback/queue/QueueFragment.kt | 72 +++++++-- .../auxio/playback/queue/QueueListFragment.kt | 145 ------------------ .../playback/state/PlaybackStateManager.kt | 4 +- .../org/oxycblt/auxio/songs/SongsFragment.kt | 2 +- .../org/oxycblt/auxio/ui/InterfaceUtils.kt | 1 - .../main/res/layout/fragment_album_detail.xml | 1 + app/src/main/res/layout/fragment_queue.xml | 24 ++- .../main/res/layout/fragment_queue_list.xml | 48 ------ app/src/main/res/layout/item_header.xml | 2 +- 17 files changed, 189 insertions(+), 302 deletions(-) delete mode 100644 app/src/main/java/org/oxycblt/auxio/playback/queue/QueueListFragment.kt delete mode 100644 app/src/main/res/layout/fragment_queue_list.xml diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 4626bea77..13df0c5e6 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -120,8 +120,6 @@ class AlbumDetailFragment : Fragment() { binding.albumArtist.setBackgroundResource(R.drawable.ui_ripple) } - // TODO: Make DetailFragment scroll to song if navigated from CompactPlaybackFragment - Log.d(this::class.simpleName, "Fragment created.") return binding.root diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt index e000356fa..4a59b4dc0 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryFragment.kt @@ -85,7 +85,6 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { true } - // TODO: Add icons to overflow menu items? menu.apply { val item = findItem(R.id.action_search) val searchView = item.actionView as SearchView @@ -100,11 +99,6 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { override fun onMenuItemActionExpand(item: MenuItem): Boolean { - // When opened, update the adapter to the SearchAdapter, and make the - // sorting group invisible. The query is also reset, as if the Auxio process - // is killed in the background while still on the search adapter, then the - // search query will stick around if its opened again - // TODO: Couldn't you just try to restore the search state on restart? binding.libraryRecycler.adapter = searchAdapter setGroupVisible(R.id.group_sorting, false) libraryModel.resetQuery() @@ -113,8 +107,6 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { } override fun onMenuItemActionCollapse(item: MenuItem): Boolean { - // When closed, make the sorting icon visible again, change back to - // LibraryAdapter, and reset the query. binding.libraryRecycler.adapter = libraryAdapter setGroupVisible(R.id.group_sorting, true) libraryModel.resetQuery() @@ -188,14 +180,12 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean = false override fun onQueryTextChange(query: String): Boolean { - libraryModel.updateSearchQuery(query) + libraryModel.updateSearchQuery(query, requireContext()) return false } private fun navToItem(baseModel: BaseModel) { - // TODO: Implement shared element transitions to the DetailFragments [If possible] - // If the item is a song [That was selected through search], then update the playback // to that song instead of doing any navigation if (baseModel is Song) { diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt index 2c1b8fb1f..c4e28aade 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt @@ -1,5 +1,6 @@ package org.oxycblt.auxio.library +import android.content.Context import android.view.MenuItem import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -44,7 +45,7 @@ class LibraryViewModel : ViewModel() { } } - fun updateSearchQuery(query: String) { + fun updateSearchQuery(query: String, context: Context) { // Don't bother if the query is blank. if (query == "") { resetQuery() @@ -65,7 +66,7 @@ class LibraryViewModel : ViewModel() { val genres = musicStore.genres.filter { it.name.contains(query, true) } if (genres.isNotEmpty()) { - combined.add(Header(id = ShowMode.SHOW_GENRES.constant)) + combined.add(Header(name = context.getString(R.string.label_genres))) combined.addAll(genres) } } @@ -74,7 +75,7 @@ class LibraryViewModel : ViewModel() { val artists = musicStore.artists.filter { it.name.contains(query, true) } if (artists.isNotEmpty()) { - combined.add(Header(id = ShowMode.SHOW_ARTISTS.constant)) + combined.add(Header(name = context.getString(R.string.label_artists))) combined.addAll(artists) } } @@ -83,14 +84,14 @@ class LibraryViewModel : ViewModel() { val albums = musicStore.albums.filter { it.name.contains(query, true) } if (albums.isNotEmpty()) { - combined.add(Header(id = ShowMode.SHOW_ALBUMS.constant)) + combined.add(Header(name = context.getString(R.string.label_albums))) combined.addAll(albums) } val songs = musicStore.songs.filter { it.name.contains(query, true) } if (songs.isNotEmpty()) { - combined.add(Header(id = ShowMode.SHOW_SONGS.constant)) + combined.add(Header(name = context.getString(R.string.label_songs))) combined.addAll(songs) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt index c256fbe2d..49af0e06b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicUtils.kt @@ -8,7 +8,6 @@ import android.text.format.DateUtils import android.widget.TextView import androidx.databinding.BindingAdapter import org.oxycblt.auxio.R -import org.oxycblt.auxio.recycler.ShowMode // List of ID3 genres + Winamp extensions, each index corresponds to their int value. // There are a lot more int-genre extensions as far as Im aware, but this works for most cases. @@ -158,18 +157,3 @@ fun TextView.bindAlbumInfo(album: Album) { fun TextView.bindAlbumYear(album: Album) { text = album.year.toYear(context) } - -// Bind the text used by the header item -@BindingAdapter("headerText") -fun TextView.bindHeaderText(header: Header) { - text = context.getString( - when (header.id) { - ShowMode.SHOW_GENRES.constant -> R.string.label_genres - ShowMode.SHOW_ARTISTS.constant -> R.string.label_artists - ShowMode.SHOW_ALBUMS.constant -> R.string.label_albums - ShowMode.SHOW_SONGS.constant -> R.string.label_songs - - else -> R.string.label_artists - } - ) -} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt index efe2151e7..7995b26c1 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt @@ -9,7 +9,6 @@ import android.content.Intent import android.content.IntentFilter import android.content.pm.ServiceInfo import android.media.AudioManager -import android.os.Binder import android.os.Build import android.os.IBinder import android.os.Parcelable @@ -71,7 +70,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca return START_NOT_STICKY } - override fun onBind(intent: Intent): IBinder? = LocalBinder() + override fun onBind(intent: Intent): IBinder? = null override fun onCreate() { super.onCreate() @@ -295,9 +294,6 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca } } - // Awful Hack to get position polling to work, as exoplayer does not provide any - // onPositionChanged callback for some inane reason. - // TODO: MediaSession might have a callback for positions. Idk. private fun pollCurrentPosition() = flow { while (player.isPlaying) { emit(player.currentPosition) @@ -435,10 +431,6 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca } } - inner class LocalBinder : Binder() { - fun getService() = this@PlaybackService - } - companion object { private const val DISCONNECTED = 0 private const val CONNECTED = 1 diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 6498bc579..e6fd9ecc2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -153,39 +153,61 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { playbackManager.prev() } - // Remove a queue item, given a QueueAdapter index. + // Remove a queue OR user queue item, given a QueueAdapter index. fun removeQueueItem(adapterIndex: Int) { - // Translate the adapter indices into the correct queue indices - val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size + var index = adapterIndex.dec() - val index = adapterIndex + delta + // If the item is in the user queue, then remove it from there after accounting for the header. + if (index < mUserQueue.value!!.size) { + playbackManager.removeUserQueueItem(index) + } else { + // Translate the indices into proper queue indices if removing an item from there. + index += (mQueue.value!!.size - nextItemsInQueue.value!!.size) - playbackManager.removeQueueItem(index) + if (userQueue.value!!.isNotEmpty()) { + index -= mUserQueue.value!!.size.inc() + } + + playbackManager.removeQueueItem(index) + } } - // Move queue items, given QueueAdapter indices. - fun moveQueueItems(adapterFrom: Int, adapterTo: Int) { - // Translate the adapter indices into the correct queue indices - val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size + // Move queue OR user queue items, given QueueAdapter indices. + // I have no idea what is going on in this function, but it works, so + fun moveQueueItems(adapterFrom: Int, adapterTo: Int): Boolean { + var from = adapterFrom.dec() + var to = adapterTo.dec() - val from = adapterFrom + delta - val to = adapterTo + delta + if (from < mUserQueue.value!!.size) { - playbackManager.moveQueueItems(from, to) + if (to >= mUserQueue.value!!.size || to < 0) return false + + playbackManager.moveUserQueueItems(from, to) + } else { + if (to < 0) return false + + val delta = mQueue.value!!.size - nextItemsInQueue.value!!.size + + from += delta + to += delta + + if (userQueue.value!!.isNotEmpty()) { + if (to <= mUserQueue.value!!.size.inc()) return false + + from -= mUserQueue.value!!.size.inc() + to -= mUserQueue.value!!.size.inc() + } + + playbackManager.moveQueueItems(from, to) + } + + return true } 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. diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index 95fa61611..dd7daafc7 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -1,25 +1,53 @@ package org.oxycblt.auxio.playback.queue import android.annotation.SuppressLint +import android.util.Log import android.view.LayoutInflater import android.view.MotionEvent import android.view.ViewGroup import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.ItemQueueSongBinding +import org.oxycblt.auxio.music.BaseModel +import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.recycler.DiffCallback import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder +import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder class QueueAdapter( val touchHelper: ItemTouchHelper -) : ListAdapter(DiffCallback()) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - return ViewHolder(ItemQueueSongBinding.inflate(LayoutInflater.from(parent.context))) +) : ListAdapter(DiffCallback()) { + override fun getItemViewType(position: Int): Int { + val item = getItem(position) + + if (item is Header) { + return HeaderViewHolder.ITEM_TYPE + } else { + return QUEUE_ITEM_VIEW_TYPE + } } - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.bind(getItem(position)) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context) + QUEUE_ITEM_VIEW_TYPE -> ViewHolder( + ItemQueueSongBinding.inflate(LayoutInflater.from(parent.context)) + ) + else -> error("Someone messed with the ViewHolder item types. Tell OxygenCobalt.") + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (val item = getItem(position)) { + is Song -> (holder as ViewHolder).bind(item) + is Header -> (holder as HeaderViewHolder).bind(item) + + else -> { + Log.d(this::class.simpleName, "Bad data fed to QueueAdapter.") + } + } } // Generic ViewHolder for a queue item @@ -46,4 +74,8 @@ class QueueAdapter( } } } + + companion object { + const val QUEUE_ITEM_VIEW_TYPE = 0xA030 + } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt index 93ad1ab5d..b88933e62 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt @@ -9,13 +9,22 @@ import kotlin.math.min import kotlin.math.sign // The drag callback used for the Queue RecyclerView. -class QueueDragCallback( - private val playbackModel: PlaybackViewModel, - private val isUserQueue: Boolean -) : ItemTouchHelper.SimpleCallback( - ItemTouchHelper.UP or ItemTouchHelper.DOWN, - ItemTouchHelper.START -) { +class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouchHelper.Callback() { + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + // Make header objects unswipable by only returning the swipe flags if the ViewHolder + // is for a queue item. + return if (viewHolder is QueueAdapter.ViewHolder) { + makeFlag( + ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN + ) or makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START) + } else { + 0 + } + } + override fun interpolateOutOfBoundsScroll( recyclerView: RecyclerView, viewSize: Int, @@ -45,21 +54,11 @@ class QueueDragCallback( viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { - if (isUserQueue) { - playbackModel.moveUserQueueItems(viewHolder.adapterPosition, target.adapterPosition) - } else { - playbackModel.moveQueueItems(viewHolder.adapterPosition, target.adapterPosition) - } - - return true + return playbackModel.moveQueueItems(viewHolder.adapterPosition, target.adapterPosition) } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - if (isUserQueue) { - playbackModel.removeUserQueueItem(viewHolder.adapterPosition) - } else { - playbackModel.removeQueueItem(viewHolder.adapterPosition) - } + playbackModel.removeQueueItem(viewHolder.adapterPosition) } companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index 6b8f4618d..05174f909 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -7,11 +7,16 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController -import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentQueueBinding +import org.oxycblt.auxio.music.BaseModel +import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.playback.state.PlaybackMode +import org.oxycblt.auxio.ui.applyDivider -// TODO: Make this better class QueueFragment : Fragment() { private val playbackModel: PlaybackViewModel by activityViewModels() @@ -22,28 +27,69 @@ class QueueFragment : Fragment() { ): View? { val binding = FragmentQueueBinding.inflate(inflater) + val helper = ItemTouchHelper(QueueDragCallback(playbackModel)) + val queueAdapter = QueueAdapter(helper) + + // --- UI SETUP --- + binding.queueToolbar.setNavigationOnClickListener { findNavController().navigateUp() } - binding.queueViewpager.adapter = PagerAdapter() + binding.queueRecycler.apply { + setHasFixedSize(true) + applyDivider() + adapter = queueAdapter + helper.attachToRecyclerView(this) + } - // TODO: Add option for default queue screen - if (playbackModel.userQueue.value!!.isNotEmpty()) { - binding.queueViewpager.setCurrentItem(0, false) - } else { - binding.queueViewpager.setCurrentItem(1, false) + playbackModel.userQueue.observe(viewLifecycleOwner) { + queueAdapter.submitList(createQueueDisplay()) { + binding.queueRecycler.scrollToPosition(0) + scrollRecyclerIfNeeded(binding) + } + } + + playbackModel.nextItemsInQueue.observe(viewLifecycleOwner) { + queueAdapter.submitList(createQueueDisplay()) { + scrollRecyclerIfNeeded(binding) + } } return binding.root } - private inner class PagerAdapter : - FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle) { - override fun getItemCount(): Int = 2 + private fun createQueueDisplay(): MutableList { + val queue = mutableListOf() - override fun createFragment(position: Int): Fragment { - return QueueListFragment(position) + if (playbackModel.userQueue.value!!.isNotEmpty()) { + queue.add(Header(name = getString(R.string.label_next_user_queue))) + queue.addAll(playbackModel.userQueue.value!!) + } + + if (playbackModel.nextItemsInQueue.value!!.isNotEmpty()) { + queue.add( + Header( + name = getString( + R.string.format_next_from, + if (playbackModel.mode.value == PlaybackMode.ALL_SONGS) + getString(R.string.title_all_songs) + else + playbackModel.parent.value!!.name + ) + ) + ) + queue.addAll(playbackModel.nextItemsInQueue.value!!) + } + + return queue + } + + private fun scrollRecyclerIfNeeded(binding: FragmentQueueBinding) { + if ((binding.queueRecycler.layoutManager as LinearLayoutManager) + .findFirstVisibleItemPosition() < 1 + ) { + binding.queueRecycler.scrollToPosition(0) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueListFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueListFragment.kt deleted file mode 100644 index 174172167..000000000 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueListFragment.kt +++ /dev/null @@ -1,145 +0,0 @@ -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.ui.applyDivider - -// TODO: Unify the user/next queues into a single fragment -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) { - binding.queueHeader.text = getString( - R.string.format_next_from, - if (it == PlaybackMode.ALL_SONGS) getString(R.string.title_all_songs) - else 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 - } -} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 6a04fcee7..c45e75684 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -42,7 +42,6 @@ class PlaybackStateManager private constructor() { } private var mUserQueue = mutableListOf() set(value) { - Log.d(this::class.simpleName, "retard.") field = value callbacks.forEach { it.onUserQueueUpdate(value) } } @@ -174,7 +173,8 @@ class PlaybackStateManager private constructor() { mMode = PlaybackMode.IN_GENRE } - else -> error("what") + else -> { + } } resetLoopMode() diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt index 2d034b0fa..4044e7661 100644 --- a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt @@ -27,7 +27,7 @@ class SongsFragment : Fragment() { val musicStore = MusicStore.getInstance() - // TODO: Add option to search songs if LibraryFragment isn't enabled + // TODO: Add option to search songs [Or just make a dedicated tab] // TODO: Fast scrolling? // --- UI SETUP --- diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt index cdd85e4ad..249d809b5 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt @@ -26,7 +26,6 @@ fun showActionMenuForSong( view: View, playbackModel: PlaybackViewModel ) { - // TODO: Replace this with a BottomSheet dialog? PopupMenu(context, view).apply { inflate(R.menu.menu_song_actions) setOnMenuItemClickListener { diff --git a/app/src/main/res/layout/fragment_album_detail.xml b/app/src/main/res/layout/fragment_album_detail.xml index 6935f09fa..81d112c49 100644 --- a/app/src/main/res/layout/fragment_album_detail.xml +++ b/app/src/main/res/layout/fragment_album_detail.xml @@ -37,6 +37,7 @@ app:title="@string/title_library_fragment" /> diff --git a/app/src/main/res/layout/fragment_queue.xml b/app/src/main/res/layout/fragment_queue.xml index 5d4a48e09..80665dfeb 100644 --- a/app/src/main/res/layout/fragment_queue.xml +++ b/app/src/main/res/layout/fragment_queue.xml @@ -6,7 +6,9 @@ + android:orientation="vertical" + android:background="@color/background" + android:animateLayoutChanges="true"> - + 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" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_queue_list.xml b/app/src/main/res/layout/fragment_queue_list.xml deleted file mode 100644 index 3070cfe12..000000000 --- a/app/src/main/res/layout/fragment_queue_list.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_header.xml b/app/src/main/res/layout/item_header.xml index 99df0d600..111965f2d 100644 --- a/app/src/main/res/layout/item_header.xml +++ b/app/src/main/res/layout/item_header.xml @@ -27,7 +27,7 @@ android:paddingBottom="@dimen/padding_small" android:textAppearance="@style/TextAppearance.MaterialComponents.Overline" android:textSize="16sp" - app:headerText="@{header}" + android:text="@{header.name}" tools:text="Songs" />