queue: rework internal queue system

Rework the queue internally to decouple the queue from playback and
better respond to reshuffling.

This is being implemented under the assumption that I will be
implementing the sliding queue eventually.
This commit is contained in:
OxygenCobalt 2022-07-26 10:57:24 -06:00
parent 496b72ca78
commit 6c59a03042
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
13 changed files with 178 additions and 91 deletions

View file

@ -28,10 +28,10 @@ import org.oxycblt.auxio.databinding.ItemSortHeaderBinding
import org.oxycblt.auxio.ui.recycler.AsyncBackingData import org.oxycblt.auxio.ui.recycler.AsyncBackingData
import org.oxycblt.auxio.ui.recycler.BindingViewHolder import org.oxycblt.auxio.ui.recycler.BindingViewHolder
import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.HeaderViewHolder
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.MultiAdapter import org.oxycblt.auxio.ui.recycler.MultiAdapter
import org.oxycblt.auxio.ui.recycler.NewHeaderViewHolder
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
@ -70,14 +70,14 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
override fun getCreatorFromItem(item: Item) = override fun getCreatorFromItem(item: Item) =
when (item) { when (item) {
is Header -> NewHeaderViewHolder.CREATOR is Header -> HeaderViewHolder.CREATOR
is SortHeader -> SortHeaderViewHolder.CREATOR is SortHeader -> SortHeaderViewHolder.CREATOR
else -> null else -> null
} }
override fun getCreatorFromViewType(viewType: Int) = override fun getCreatorFromViewType(viewType: Int) =
when (viewType) { when (viewType) {
NewHeaderViewHolder.CREATOR.viewType -> NewHeaderViewHolder.CREATOR HeaderViewHolder.CREATOR.viewType -> HeaderViewHolder.CREATOR
SortHeaderViewHolder.CREATOR.viewType -> SortHeaderViewHolder.CREATOR SortHeaderViewHolder.CREATOR.viewType -> SortHeaderViewHolder.CREATOR
else -> null else -> null
} }
@ -90,7 +90,7 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
) { ) {
if (payload.isEmpty()) { if (payload.isEmpty()) {
when (item) { when (item) {
is Header -> (viewHolder as NewHeaderViewHolder).bind(item, Unit) is Header -> (viewHolder as HeaderViewHolder).bind(item, Unit)
is SortHeader -> (viewHolder as SortHeaderViewHolder).bind(item, listener) is SortHeader -> (viewHolder as SortHeaderViewHolder).bind(item, listener)
} }
} }
@ -111,7 +111,7 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean { override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return when { return when {
oldItem is Header && newItem is Header -> oldItem is Header && newItem is Header ->
NewHeaderViewHolder.DIFFER.areItemsTheSame(oldItem, newItem) HeaderViewHolder.DIFFER.areItemsTheSame(oldItem, newItem)
oldItem is SortHeader && newItem is SortHeader -> oldItem is SortHeader && newItem is SortHeader ->
SortHeaderViewHolder.DIFFER.areItemsTheSame(oldItem, newItem) SortHeaderViewHolder.DIFFER.areItemsTheSame(oldItem, newItem)
else -> false else -> false

View file

@ -114,8 +114,6 @@ class PlaybackPanelFragment :
collectImmediately(playbackModel.repeatMode, ::updateRepeat) collectImmediately(playbackModel.repeatMode, ::updateRepeat)
collectImmediately(playbackModel.isPlaying, ::updatePlaying) collectImmediately(playbackModel.isPlaying, ::updatePlaying)
collectImmediately(playbackModel.isShuffled, ::updateShuffled) collectImmediately(playbackModel.isShuffled, ::updateShuffled)
collectImmediately(playbackModel.nextUp, ::updateNextUp)
logD("Fragment Created") logD("Fragment Created")
} }
@ -195,8 +193,4 @@ class PlaybackPanelFragment :
private fun updateShuffled(isShuffled: Boolean) { private fun updateShuffled(isShuffled: Boolean) {
requireBinding().playbackShuffle.isActivated = isShuffled requireBinding().playbackShuffle.isActivated = isShuffled
} }
private fun updateNextUp(nextUp: List<Song>) {
queueItem.isEnabled = nextUp.isNotEmpty()
}
} }

View file

@ -36,7 +36,6 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.application import org.oxycblt.auxio.util.application
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
/** /**
@ -77,11 +76,6 @@ class PlaybackViewModel(application: Application) :
val isShuffled: StateFlow<Boolean> val isShuffled: StateFlow<Boolean>
get() = _isShuffled get() = _isShuffled
private val _nextUp = MutableStateFlow(listOf<Song>())
/** The queue, without the previous items. */
val nextUp: StateFlow<List<Song>>
get() = _nextUp
init { init {
musicStore.addCallback(this) musicStore.addCallback(this)
playbackManager.addCallback(this) playbackManager.addCallback(this)
@ -196,39 +190,6 @@ class PlaybackViewModel(application: Application) :
playbackManager.prev() playbackManager.prev()
} }
/**
* Go to an item in the queue using it's recyclerview adapter index. No-ops if out of bounds.
*/
fun goto(adapterIndex: Int) {
val index = adapterIndex + (playbackManager.queue.size - _nextUp.value.size)
logD(adapterIndex)
logD(playbackManager.queue.size - _nextUp.value.size)
if (index in playbackManager.queue.indices) {
playbackManager.goto(index)
}
}
/** Remove a queue item using it's recyclerview adapter index. */
fun removeQueueDataItem(adapterIndex: Int) {
val index = adapterIndex + (playbackManager.queue.size - _nextUp.value.size)
if (index in playbackManager.queue.indices) {
playbackManager.removeQueueItem(index)
}
}
/** Move queue items using their recyclerview adapter indices. */
fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int): Boolean {
val delta = (playbackManager.queue.size - _nextUp.value.size)
val from = adapterFrom + delta
val to = adapterTo + delta
if (from in playbackManager.queue.indices && to in playbackManager.queue.indices) {
playbackManager.moveQueueItem(from, to)
return true
}
return false
}
/** Add a [Song] to the top of the queue. */ /** Add a [Song] to the top of the queue. */
fun playNext(song: Song) { fun playNext(song: Song) {
playbackManager.playNext(song) playbackManager.playNext(song)
@ -334,17 +295,11 @@ class PlaybackViewModel(application: Application) :
override fun onIndexMoved(index: Int) { override fun onIndexMoved(index: Int) {
_song.value = playbackManager.song _song.value = playbackManager.song
_nextUp.value = playbackManager.queue.slice(index + 1 until playbackManager.queue.size)
}
override fun onQueueChanged(index: Int, queue: List<Song>) {
_nextUp.value = queue.slice(index + 1 until queue.size)
} }
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) { override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
_song.value = playbackManager.song _song.value = playbackManager.song
_parent.value = playbackManager.parent _parent.value = playbackManager.parent
_nextUp.value = queue.slice(index + 1 until queue.size)
} }
override fun onPositionChanged(positionMs: Long) { override fun onPositionChanged(positionMs: Long) {

View file

@ -31,7 +31,7 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.recycler.* import org.oxycblt.auxio.ui.recycler.*
import org.oxycblt.auxio.util.* import org.oxycblt.auxio.util.*
class QueueAdapter(private val listener: QueueItemListener) : class QueueAdapter(listener: QueueItemListener) :
MonoAdapter<Song, QueueItemListener, QueueSongViewHolder>(listener) { MonoAdapter<Song, QueueItemListener, QueueSongViewHolder>(listener) {
override val data = SyncBackingData(this, QueueSongViewHolder.DIFFER) override val data = SyncBackingData(this, QueueSongViewHolder.DIFFER)
override val creator = QueueSongViewHolder.CREATOR override val creator = QueueSongViewHolder.CREATOR

View file

@ -27,7 +27,6 @@ import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.sign import kotlin.math.sign
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.getDimenSafe import org.oxycblt.auxio.util.getDimenSafe
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -37,7 +36,7 @@ import org.oxycblt.auxio.util.logD
* hot garbage. This shouldn't have *too many* UI bugs. I hope. * hot garbage. This shouldn't have *too many* UI bugs. I hope.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouchHelper.Callback() { class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHelper.Callback() {
private var shouldLift = true private var shouldLift = true
override fun getMovementFlags( override fun getMovementFlags(

View file

@ -20,14 +20,12 @@ package org.oxycblt.auxio.playback.queue
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
/** /**
@ -35,10 +33,10 @@ import org.oxycblt.auxio.util.collectImmediately
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemListener { class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemListener {
private val playbackModel: PlaybackViewModel by androidActivityViewModels() private val queueModel: QueueViewModel by activityViewModels()
private val queueAdapter = QueueAdapter(this) private val queueAdapter = QueueAdapter(this)
private val touchHelper: ItemTouchHelper by lifecycleObject { private val touchHelper: ItemTouchHelper by lifecycleObject {
ItemTouchHelper(QueueDragCallback(playbackModel)) ItemTouchHelper(QueueDragCallback(queueModel))
} }
override fun onCreateBinding(inflater: LayoutInflater) = FragmentQueueBinding.inflate(inflater) override fun onCreateBinding(inflater: LayoutInflater) = FragmentQueueBinding.inflate(inflater)
@ -53,7 +51,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
// --- VIEWMODEL SETUP ---- // --- VIEWMODEL SETUP ----
collectImmediately(playbackModel.nextUp, ::updateQueue) collectImmediately(queueModel.queue, ::updateQueue)
} }
override fun onDestroyBinding(binding: FragmentQueueBinding) { override fun onDestroyBinding(binding: FragmentQueueBinding) {
@ -62,19 +60,20 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
} }
override fun onClick(viewHolder: RecyclerView.ViewHolder) { override fun onClick(viewHolder: RecyclerView.ViewHolder) {
playbackModel.goto(viewHolder.bindingAdapterPosition) queueModel.goto(viewHolder.bindingAdapterPosition)
} }
override fun onPickUp(viewHolder: RecyclerView.ViewHolder) { override fun onPickUp(viewHolder: RecyclerView.ViewHolder) {
touchHelper.startDrag(viewHolder) touchHelper.startDrag(viewHolder)
} }
private fun updateQueue(queue: List<Song>) { private fun updateQueue(queue: QueueViewModel.QueueData) {
if (queue.isEmpty()) { if (queue.nonTrivial) {
findNavController().navigateUp() // nonTrivial implies that using a synced submitList would be slow, replace the list
return // instead.
queueAdapter.data.replaceList(queue.queue)
} else {
queueAdapter.data.submitList(queue.queue)
} }
queueAdapter.data.submitList(queue.toMutableList())
} }
} }

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) 2022 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playback.queue
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.util.logD
class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
private val playbackManager = PlaybackStateManager.getInstance()
data class QueueData(val queue: List<Song>, val nonTrivial: Boolean)
private val _queue = MutableStateFlow(QueueData(listOf(), false))
val queue: StateFlow<QueueData> = _queue
init {
playbackManager.addCallback(this)
}
/**
* Go to an item in the queue using it's recyclerview adapter index. No-ops if out of bounds.
*/
fun goto(adapterIndex: Int) {
val index = adapterIndex + (playbackManager.queue.size - _queue.value.queue.size)
logD(adapterIndex)
logD(playbackManager.queue.size - _queue.value.queue.size)
if (index in playbackManager.queue.indices) {
playbackManager.goto(index)
}
}
/** Remove a queue item using it's recyclerview adapter index. */
fun removeQueueDataItem(adapterIndex: Int) {
val index = adapterIndex + (playbackManager.queue.size - _queue.value.queue.size)
if (index in playbackManager.queue.indices) {
playbackManager.removeQueueItem(index)
}
}
/** Move queue items using their recyclerview adapter indices. */
fun moveQueueDataItems(adapterFrom: Int, adapterTo: Int): Boolean {
val delta = (playbackManager.queue.size - _queue.value.queue.size)
val from = adapterFrom + delta
val to = adapterTo + delta
if (from in playbackManager.queue.indices && to in playbackManager.queue.indices) {
playbackManager.moveQueueItem(from, to)
return true
}
return false
}
override fun onIndexMoved(index: Int) {
_queue.value = QueueData(generateQueue(index, playbackManager.queue), false)
}
override fun onQueueChanged(queue: List<Song>) {
_queue.value = QueueData(generateQueue(playbackManager.index, queue), false)
}
override fun onQueueReworked(index: Int, queue: List<Song>) {
_queue.value = QueueData(generateQueue(index, queue), true)
}
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
_queue.value = QueueData(generateQueue(index, queue), true)
}
private fun generateQueue(index: Int, queue: List<Song>) =
queue.slice(index + 1..playbackManager.queue.lastIndex)
override fun onCleared() {
super.onCleared()
playbackManager.removeCallback(this)
}
}

View file

@ -280,7 +280,7 @@ class PlaybackStateManager private constructor() {
val library = musicStore.library ?: return val library = musicStore.library ?: return
val song = song ?: return val song = song ?: return
applyNewQueue(library, settings, shuffled, song) applyNewQueue(library, settings, shuffled, song)
notifyQueueChanged() notifyQueueReworked()
notifyShuffledChanged() notifyShuffledChanged()
} }
@ -464,7 +464,13 @@ class PlaybackStateManager private constructor() {
private fun notifyQueueChanged() { private fun notifyQueueChanged() {
for (callback in callbacks) { for (callback in callbacks) {
callback.onQueueChanged(index, queue) callback.onQueueChanged(queue)
}
}
private fun notifyQueueReworked() {
for (callback in callbacks) {
callback.onQueueReworked(index, queue)
} }
} }
@ -529,8 +535,11 @@ class PlaybackStateManager private constructor() {
/** Called when the index is moved, but the queue does not change. This changes the song. */ /** Called when the index is moved, but the queue does not change. This changes the song. */
fun onIndexMoved(index: Int) {} fun onIndexMoved(index: Int) {}
/** Called when the queue and/or index changed, but the song has not. */ /** Called when the queue has changed in a way that does not change the index or song. */
fun onQueueChanged(index: Int, queue: List<Song>) {} fun onQueueChanged(queue: List<Song>) {}
/** Called when the queue and index has changed, but the song has not changed.. */
fun onQueueReworked(index: Int, queue: List<Song>) {}
/** Called when playback is changed completely, with a new index, queue, and parent. */ /** Called when playback is changed completely, with a new index, queue, and parent. */
fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {} fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {}

View file

@ -20,6 +20,8 @@ package org.oxycblt.auxio.playback.system
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.os.SystemClock import android.os.SystemClock
import android.support.v4.media.MediaDescriptionCompat import android.support.v4.media.MediaDescriptionCompat
import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.MediaMetadataCompat
@ -101,10 +103,15 @@ class MediaSessionComponent(
invalidateSessionState() invalidateSessionState()
} }
override fun onQueueChanged(index: Int, queue: List<Song>) { override fun onQueueChanged(queue: List<Song>) {
updateQueue(queue) updateQueue(queue)
} }
override fun onQueueReworked(index: Int, queue: List<Song>) {
updateQueue(queue)
invalidateSessionState()
}
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) { override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
updateMediaMetadata(playbackManager.song, parent) updateMediaMetadata(playbackManager.song, parent)
updateQueue(queue) updateQueue(queue)
@ -118,8 +125,8 @@ class MediaSessionComponent(
return return
} }
// We would leave the artist field null if it didn't exist and let downstream consumers // Note: We would leave the artist field null if it didn't exist and let downstream
// handle it, but that would break the notification display. // consumers handle it, but that would break the notification display.
val title = song.resolveName(context) val title = song.resolveName(context)
val artist = song.resolveIndividualArtistName(context) val artist = song.resolveIndividualArtistName(context)
val builder = val builder =
@ -180,11 +187,11 @@ class MediaSessionComponent(
private fun updateQueue(queue: List<Song>) { private fun updateQueue(queue: List<Song>) {
val queueItems = val queueItems =
queue.mapIndexed { i, song -> queue.mapIndexed { i, song ->
// Since we usually have to load many songs into the queue, use the Cover URI // Since we usually have to load many songs into the queue, use the MediaStore URI
// instead of loading a bitmap. // instead of loading a bitmap.
val description = val description =
MediaDescriptionCompat.Builder() MediaDescriptionCompat.Builder()
.setMediaId(song.id.toString()) .setMediaId("Song:${song.id}")
.setTitle(song.resolveName(context)) .setTitle(song.resolveName(context))
.setSubtitle(song.resolveIndividualArtistName(context)) .setSubtitle(song.resolveIndividualArtistName(context))
.setIconUri(song.album.coverUri) .setIconUri(song.album.coverUri)
@ -245,8 +252,8 @@ class MediaSessionComponent(
invalidateSessionState() invalidateSessionState()
if (!playbackManager.isPlaying) { if (!playbackManager.isPlaying) {
// Hack around issue where the position won't update after a seek (but only when it's // Hack around issue where the position won't update after a seek when paused.
// paused). Apparently this can be fixed by re-posting the notification, but not always // Apparently this can be fixed by re-posting the notification, but not always
// when we invalidate the state (that will cause us to be rate-limited), and also not // when we invalidate the state (that will cause us to be rate-limited), and also not
// always when we seek (that will also cause us to be rate-limited). Someone looked at // always when we seek (that will also cause us to be rate-limited). Someone looked at
// this system and said it was well-designed. // this system and said it was well-designed.
@ -256,6 +263,31 @@ class MediaSessionComponent(
// --- MEDIASESSION CALLBACKS --- // --- MEDIASESSION CALLBACKS ---
override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
super.onPlayFromMediaId(mediaId, extras)
// STUB: Unimplemented
}
override fun onPlayFromUri(uri: Uri?, extras: Bundle?) {
super.onPlayFromUri(uri, extras)
// STUB: Unimplemented
}
override fun onPlayFromSearch(query: String?, extras: Bundle?) {
super.onPlayFromSearch(query, extras)
// STUB: Unimplemented
}
override fun onAddQueueItem(description: MediaDescriptionCompat?) {
super.onAddQueueItem(description)
// STUB: Unimplemented
}
override fun onRemoveQueueItem(description: MediaDescriptionCompat?) {
super.onRemoveQueueItem(description)
// STUB: Unimplemented
}
override fun onPlay() { override fun onPlay() {
playbackManager.isPlaying = true playbackManager.isPlaying = true
} }

View file

@ -27,10 +27,10 @@ import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
import org.oxycblt.auxio.ui.recycler.AsyncBackingData import org.oxycblt.auxio.ui.recycler.AsyncBackingData
import org.oxycblt.auxio.ui.recycler.GenreViewHolder import org.oxycblt.auxio.ui.recycler.GenreViewHolder
import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Header
import org.oxycblt.auxio.ui.recycler.HeaderViewHolder
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.MultiAdapter import org.oxycblt.auxio.ui.recycler.MultiAdapter
import org.oxycblt.auxio.ui.recycler.NewHeaderViewHolder
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
import org.oxycblt.auxio.ui.recycler.SongViewHolder import org.oxycblt.auxio.ui.recycler.SongViewHolder
@ -43,7 +43,7 @@ class SearchAdapter(listener: MenuItemListener) : MultiAdapter<MenuItemListener>
is Album -> AlbumViewHolder.CREATOR is Album -> AlbumViewHolder.CREATOR
is Artist -> ArtistViewHolder.CREATOR is Artist -> ArtistViewHolder.CREATOR
is Genre -> GenreViewHolder.CREATOR is Genre -> GenreViewHolder.CREATOR
is Header -> NewHeaderViewHolder.CREATOR is Header -> HeaderViewHolder.CREATOR
else -> null else -> null
} }
@ -53,7 +53,7 @@ class SearchAdapter(listener: MenuItemListener) : MultiAdapter<MenuItemListener>
AlbumViewHolder.CREATOR.viewType -> AlbumViewHolder.CREATOR AlbumViewHolder.CREATOR.viewType -> AlbumViewHolder.CREATOR
ArtistViewHolder.CREATOR.viewType -> ArtistViewHolder.CREATOR ArtistViewHolder.CREATOR.viewType -> ArtistViewHolder.CREATOR
GenreViewHolder.CREATOR.viewType -> GenreViewHolder.CREATOR GenreViewHolder.CREATOR.viewType -> GenreViewHolder.CREATOR
NewHeaderViewHolder.CREATOR.viewType -> NewHeaderViewHolder.CREATOR HeaderViewHolder.CREATOR.viewType -> HeaderViewHolder.CREATOR
else -> null else -> null
} }
@ -68,7 +68,7 @@ class SearchAdapter(listener: MenuItemListener) : MultiAdapter<MenuItemListener>
is Album -> (viewHolder as AlbumViewHolder).bind(item, listener) is Album -> (viewHolder as AlbumViewHolder).bind(item, listener)
is Artist -> (viewHolder as ArtistViewHolder).bind(item, listener) is Artist -> (viewHolder as ArtistViewHolder).bind(item, listener)
is Genre -> (viewHolder as GenreViewHolder).bind(item, listener) is Genre -> (viewHolder as GenreViewHolder).bind(item, listener)
is Header -> (viewHolder as NewHeaderViewHolder).bind(item, Unit) is Header -> (viewHolder as HeaderViewHolder).bind(item, Unit)
else -> {} else -> {}
} }
} }
@ -87,7 +87,7 @@ class SearchAdapter(listener: MenuItemListener) : MultiAdapter<MenuItemListener>
oldItem is Genre && newItem is Genre -> oldItem is Genre && newItem is Genre ->
GenreViewHolder.DIFFER.areItemsTheSame(oldItem, newItem) GenreViewHolder.DIFFER.areItemsTheSame(oldItem, newItem)
oldItem is Header && newItem is Header -> oldItem is Header && newItem is Header ->
NewHeaderViewHolder.DIFFER.areItemsTheSame(oldItem, newItem) HeaderViewHolder.DIFFER.areItemsTheSame(oldItem, newItem)
else -> false else -> false
} }
} }

View file

@ -201,7 +201,7 @@ private constructor(
* The Shared ViewHolder for a [Header]. * The Shared ViewHolder for a [Header].
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class NewHeaderViewHolder private constructor(private val binding: ItemHeaderBinding) : class HeaderViewHolder private constructor(private val binding: ItemHeaderBinding) :
BindingViewHolder<Header, Unit>(binding.root) { BindingViewHolder<Header, Unit>(binding.root) {
override fun bind(item: Header, listener: Unit) { override fun bind(item: Header, listener: Unit) {
@ -210,12 +210,12 @@ class NewHeaderViewHolder private constructor(private val binding: ItemHeaderBin
companion object { companion object {
val CREATOR = val CREATOR =
object : Creator<NewHeaderViewHolder> { object : Creator<HeaderViewHolder> {
override val viewType: Int override val viewType: Int
get() = IntegerTable.ITEM_TYPE_HEADER get() = IntegerTable.ITEM_TYPE_HEADER
override fun create(context: Context) = override fun create(context: Context) =
NewHeaderViewHolder(ItemHeaderBinding.inflate(context.inflater)) HeaderViewHolder(ItemHeaderBinding.inflate(context.inflater))
} }
val DIFFER = val DIFFER =

View file

@ -27,6 +27,7 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/interact_body"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"> android:background="?attr/selectableItemBackground">

View file

@ -81,6 +81,8 @@
<string name="lbl_shuffle">Shuffle</string> <string name="lbl_shuffle">Shuffle</string>
<string name="lbl_queue">Queue</string> <string name="lbl_queue">Queue</string>
<string name="lbl_next_up">Next up</string>
<string name="lbl_later">Later</string>
<string name="lbl_play_next">Play next</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>