diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 5c6178b16..4b5d9d414 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -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> get() = _genres + private val _selected = MutableStateFlow(listOf()) + val selected: StateFlow> + get() = _selected + var tabs: List = 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]}") diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt index d6cd368f9..7905ae5fb 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt @@ -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() { } } - 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) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt index e82a6362c..b39a27443 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt @@ -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() { } } - 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) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt index 89adb1f70..c9a683428 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt @@ -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() { } } - 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) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt index c95ccfed2..9156b2e46 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListFragment.kt @@ -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 : 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) + } + } + } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index 1f01f36ca..cb9ebf03c 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -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() { } 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() { } } - 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() { } private class SongAdapter(private val listener: MenuItemListener) : - PlayingIndicatorAdapter() { + SelectionIndicatorAdapter() { private val differ = SyncListDiffer(this, SongViewHolder.DIFFER) override val currentList: List diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt index 8408c487d..2515feae3 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/Data.kt @@ -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) } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/SelectionIndicatorAdapter.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/SelectionIndicatorAdapter.kt index 8e4ecbd16..283ef3d55 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/SelectionIndicatorAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/SelectionIndicatorAdapter.kt @@ -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 : Playing } } - fun updateSelection(items: Set) { + fun updateSelection(items: List) { 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 : 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) } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt b/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt index 81457db86..682c2e876 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/recycler/ViewHolders.kt @@ -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