home: add selection framework

Add the basic selection flow to the home UI.

This has no function yet. Further work needs to be done first.
This commit is contained in:
Alexander Capehart 2022-11-22 16:21:28 -07:00
parent 361ca422e3
commit 6f05697088
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
9 changed files with 83 additions and 22 deletions

View file

@ -18,9 +18,11 @@
package org.oxycblt.auxio.home
import android.app.Application
import androidx.collection.arraySetOf
import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.getAndUpdate
import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.music.Album
@ -60,6 +62,10 @@ class HomeViewModel(application: Application) :
val genres: StateFlow<List<Genre>>
get() = _genres
private val _selected = MutableStateFlow(listOf<Music>())
val selected: StateFlow<List<Music>>
get() = _selected
var tabs: List<MusicMode> = visibleTabs
private set
@ -84,6 +90,19 @@ class HomeViewModel(application: Application) :
musicStore.addCallback(this)
}
/** Select a music item. */
fun select(item: Music) {
val items = _selected.value.toMutableList()
if (items.remove(item)) {
logD("Unselecting item $item")
_selected.value = items
} else {
logD("Selecting item $item")
items.add(item)
_selected.value = items
}
}
/** Update the current tab based off of the new ViewPager position. */
fun updateCurrentTab(pos: Int) {
logD("Updating current tab to ${tabs[pos]}")

View file

@ -25,6 +25,7 @@ import java.util.Formatter
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort
@ -97,9 +98,9 @@ class AlbumListFragment : HomeListFragment<Album>() {
}
}
override fun onItemClick(item: Item) {
check(item is Album) { "Unexpected datatype: ${item::class.java}" }
navModel.exploreNavigateTo(item)
override fun onItemClick(music: Music) {
check(music is Album) { "Unexpected datatype: ${music::class.java}" }
navModel.exploreNavigateTo(music)
}
override fun onOpenMenu(item: Item, anchor: View) {

View file

@ -23,6 +23,7 @@ import android.view.ViewGroup
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort
@ -73,9 +74,9 @@ class ArtistListFragment : HomeListFragment<Artist>() {
}
}
override fun onItemClick(item: Item) {
check(item is Artist) { "Unexpected datatype: ${item::class.java}" }
navModel.exploreNavigateTo(item)
override fun onItemClick(music: Music) {
check(music is Artist) { "Unexpected datatype: ${music::class.java}" }
navModel.exploreNavigateTo(music)
}
override fun onOpenMenu(item: Item, anchor: View) {

View file

@ -23,6 +23,7 @@ import android.view.ViewGroup
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Sort
@ -72,9 +73,9 @@ class GenreListFragment : HomeListFragment<Genre>() {
}
}
override fun onItemClick(item: Item) {
check(item is Genre) { "Unexpected datatype: ${item::class.java}" }
navModel.exploreNavigateTo(item)
override fun onItemClick(music: Music) {
check(music is Genre) { "Unexpected datatype: ${music::class.java}" }
navModel.exploreNavigateTo(music)
}
override fun onOpenMenu(item: Item, anchor: View) {

View file

@ -22,6 +22,8 @@ import android.view.LayoutInflater
import androidx.fragment.app.Fragment
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.ui.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.ui.recycler.Item
@ -63,4 +65,25 @@ abstract class HomeListFragment<T : Item> :
override fun onFastScrollStop() {
homeModel.updateFastScrolling(false)
}
abstract fun onItemClick(music: Music)
override fun onItemClick(item: Item) {
check(item is Music) { "Unexpected datatype: ${item::class.java}" }
if(homeModel.selected.value.isEmpty()) {
onItemClick(item)
} else {
homeModel.select(item)
}
}
override fun onItemLongClick(item: Item) {
check(item is Music) { "Unexpected datatype: ${item::class.java}" }
if (homeModel.selected.value.isEmpty()) {
homeModel.select(item)
} else {
onItemClick(item)
}
}
}

View file

@ -24,6 +24,7 @@ import android.view.ViewGroup
import java.util.Formatter
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
@ -34,6 +35,7 @@ import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.recycler.PlayingIndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.ui.recycler.SongViewHolder
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
import org.oxycblt.auxio.util.collectImmediately
@ -58,6 +60,7 @@ class SongListFragment : HomeListFragment<Song>() {
}
collectImmediately(homeModel.songs, homeAdapter::replaceList)
collectImmediately(homeModel.selected, homeAdapter::updateSelection)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::handlePlayback)
}
@ -104,12 +107,12 @@ class SongListFragment : HomeListFragment<Song>() {
}
}
override fun onItemClick(item: Item) {
check(item is Song) { "Unexpected datatype: ${item::class.java}" }
override fun onItemClick(music: Music) {
check(music is Song) { "Unexpected datatype: ${music::class.java}" }
when (settings.libPlaybackMode) {
MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.SONGS -> playbackModel.playFromAll(music)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
else -> error("Unexpected playback mode: ${settings.libPlaybackMode}")
}
}
@ -129,7 +132,7 @@ class SongListFragment : HomeListFragment<Song>() {
}
private class SongAdapter(private val listener: MenuItemListener) :
PlayingIndicatorAdapter<SongViewHolder>() {
SelectionIndicatorAdapter<SongViewHolder>() {
private val differ = SyncListDiffer(this, SongViewHolder.DIFFER)
override val currentList: List<Item>

View file

@ -40,6 +40,9 @@ interface ItemClickListener {
/** An interface for detecting if an item has had it's menu opened. */
interface MenuItemListener : ItemClickListener {
/** Called when an item is long-clicked. */
fun onItemLongClick(item: Item) {}
/** Called when an item desires to open a menu relating to it. */
fun onOpenMenu(item: Item, anchor: View)
}

View file

@ -3,6 +3,7 @@ package org.oxycblt.auxio.ui.recycler
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.util.logD
/**
* An adapter that implements selection indicators.
@ -23,14 +24,14 @@ abstract class SelectionIndicatorAdapter<VH : RecyclerView.ViewHolder> : Playing
}
}
fun updateSelection(items: Set<Music>) {
fun updateSelection(items: List<Music>) {
val oldSelectedItems = selectedItems
if (items == oldSelectedItems) {
val newSelectedItems = items.toSet()
if (newSelectedItems == oldSelectedItems) {
return
}
selectedItems = items
selectedItems = newSelectedItems
for (i in currentList.indices) {
// TODO: Perhaps add an optimization that allows me to avoid the O(n) iteration
// assuming all list items are unique?
@ -39,14 +40,16 @@ abstract class SelectionIndicatorAdapter<VH : RecyclerView.ViewHolder> : Playing
continue
}
if (oldSelectedItems.contains(item) || items.contains(item)) {
val added = !oldSelectedItems.contains(item) && newSelectedItems.contains(item)
val removed = oldSelectedItems.contains(item) && !newSelectedItems.contains(item)
if (added || removed) {
notifyItemChanged(i, PAYLOAD_INDICATOR_CHANGED)
}
}
}
/** A ViewHolder that can respond to selection indicator updates. */
abstract class ViewHolder(root: View) : RecyclerView.ViewHolder(root) {
abstract class ViewHolder(root: View) : PlayingIndicatorAdapter.ViewHolder(root) {
abstract fun updateSelectionIndicator(isSelected: Boolean)
}
}

View file

@ -31,17 +31,19 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.logD
/**
* The shared ViewHolder for a [Song].
* @author OxygenCobalt
*/
class SongViewHolder private constructor(private val binding: ItemSongBinding) :
PlayingIndicatorAdapter.ViewHolder(binding.root) {
SelectionIndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Song, listener: MenuItemListener) {
binding.songAlbumCover.bind(item)
binding.songName.text = item.resolveName(binding.context)
binding.songInfo.text = item.resolveArtistContents(binding.context)
binding.songMenu.setOnClickListener { listener.onOpenMenu(item, it) }
binding.root.setOnClickListener { listener.onItemClick(item) }
}
@ -51,6 +53,11 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
binding.songAlbumCover.isPlaying = isPlaying
}
override fun updateSelectionIndicator(isSelected: Boolean) {
logD("Selected")
binding.root.isActivated = isSelected
}
companion object {
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_SONG