detail: add selection

Add selection to the detail views.
This commit is contained in:
Alexander Capehart 2022-12-18 15:01:31 -07:00
parent 32d01f2027
commit 813daed644
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
24 changed files with 277 additions and 327 deletions

View file

@ -48,6 +48,10 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
*
* TODO: Migrate to material animation system
*
* TODO: Re-document project
*
* TODO: Unit testing
*
* @author OxygenCobalt
*/
class MainActivity : AppCompatActivity() {

View file

@ -32,7 +32,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.recycler.AlbumDetailAdapter
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.MenuFragment
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
@ -53,7 +53,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* A fragment that shows information for a particular [Album].
* @author OxygenCobalt
*/
class AlbumDetailFragment : MenuFragment<FragmentDetailBinding>() {
class AlbumDetailFragment : ListFragment<FragmentDetailBinding>() {
private val detailModel: DetailViewModel by activityViewModels()
private val args: AlbumDetailFragmentArgs by navArgs()
@ -64,7 +64,7 @@ class AlbumDetailFragment : MenuFragment<FragmentDetailBinding>() {
AlbumDetailAdapter.Callback(
::handleClick,
::handleOpenItemMenu,
{},
::handleSelect,
::handlePlay,
::handleShuffle,
::handleOpenSortMenu,
@ -81,7 +81,7 @@ class AlbumDetailFragment : MenuFragment<FragmentDetailBinding>() {
override fun onCreateBinding(inflater: LayoutInflater) = FragmentDetailBinding.inflate(inflater)
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
detailModel.setAlbumUid(args.albumUid)
setupSelectionToolbar(binding.detailSelectionToolbar)
binding.detailToolbar.apply {
inflateMenu(R.menu.menu_album_detail)
@ -96,11 +96,14 @@ class AlbumDetailFragment : MenuFragment<FragmentDetailBinding>() {
// -- VIEWMODEL SETUP ---
detailModel.setAlbumUid(args.albumUid)
collectImmediately(detailModel.currentAlbum, ::updateItem)
collectImmediately(detailModel.albumData, detailAdapter::submitList)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(navModel.exploreNavigationItem, ::handleNavigation)
collectImmediately(selectionModel.selected, ::handleSelection)
}
override fun onDestroyBinding(binding: FragmentDetailBinding) {
@ -108,13 +111,13 @@ class AlbumDetailFragment : MenuFragment<FragmentDetailBinding>() {
binding.detailRecycler.adapter = null
}
private fun handleClick(item: Item) {
check(item is Song) { "Unexpected datatype: ${item::class.simpleName}" }
override fun onRealClick(music: Music) {
check(music is Song) { "Unexpected datatype: ${music::class.java}"}
when (settings.detailPlaybackMode) {
null,
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
MusicMode.SONGS -> playbackModel.playFromAll(music)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
else -> error("Unexpected playback mode: ${settings.detailPlaybackMode}")
}
}
@ -179,6 +182,26 @@ class AlbumDetailFragment : MenuFragment<FragmentDetailBinding>() {
requireBinding().detailToolbar.title = album.resolveName(requireContext())
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
val binding = requireBinding()
for (item in binding.detailToolbar.menu.children) {
// If there is no playback going in, any queue additions will be wiped as soon as
// something is played. Disable these actions when playback is going on so that
// it isn't possible to add anything during that time.
if (item.itemId == R.id.action_play_next || item.itemId == R.id.action_queue_add) {
item.isEnabled = song != null
}
}
if (parent is Album && parent == unlikelyToBeNull(detailModel.currentAlbum.value)) {
detailAdapter.setPlayingItem(song, isPlaying)
} else {
// Clear the ViewHolders if the mode isn't ALL_SONGS
detailAdapter.setPlayingItem(null, isPlaying)
}
}
private fun handleNavigation(item: Music?) {
val binding = requireBinding()
when (item) {
@ -221,7 +244,7 @@ class AlbumDetailFragment : MenuFragment<FragmentDetailBinding>() {
}
}
/** Scroll to an [song]. */
/** Scroll to a [song]. */
private fun scrollToItem(song: Song) {
// Calculate where the item for the currently played song is
val pos = detailModel.albumData.value.indexOf(song)
@ -241,24 +264,9 @@ class AlbumDetailFragment : MenuFragment<FragmentDetailBinding>() {
}
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
val binding = requireBinding()
for (item in binding.detailToolbar.menu.children) {
// If there is no playback going in, any queue additions will be wiped as soon as
// something is played. Disable these actions when playback is going on so that
// it isn't possible to add anything during that time.
if (item.itemId == R.id.action_play_next || item.itemId == R.id.action_queue_add) {
item.isEnabled = song != null
}
}
if (parent is Album && parent == unlikelyToBeNull(detailModel.currentAlbum.value)) {
detailAdapter.updateIndicator(song, isPlaying)
} else {
// Clear the ViewHolders if the mode isn't ALL_SONGS
detailAdapter.updateIndicator(null, isPlaying)
}
private fun handleSelection(selected: List<Music>) {
detailAdapter.setSelectedItems(selected)
requireBinding().detailSelectionToolbar.updateSelectionAmount(selected.size)
}
/**
@ -279,4 +287,5 @@ class AlbumDetailFragment : MenuFragment<FragmentDetailBinding>() {
snapPreference: Int
): Int = (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2)
}
}

View file

@ -30,7 +30,7 @@ import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.recycler.ArtistDetailAdapter
import org.oxycblt.auxio.detail.recycler.DetailAdapter
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.MenuFragment
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Music
@ -50,7 +50,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* A fragment that shows information for a particular [Artist].
* @author OxygenCobalt
*/
class ArtistDetailFragment : MenuFragment<FragmentDetailBinding>() {
class ArtistDetailFragment : ListFragment<FragmentDetailBinding>() {
private val detailModel: DetailViewModel by activityViewModels()
private val args: ArtistDetailFragmentArgs by navArgs()
@ -61,7 +61,7 @@ class ArtistDetailFragment : MenuFragment<FragmentDetailBinding>() {
DetailAdapter.Callback(
::handleClick,
::handleOpenItemMenu,
{},
::handleSelect,
::handlePlay,
::handleShuffle,
::handleOpenSortMenu))
@ -77,7 +77,7 @@ class ArtistDetailFragment : MenuFragment<FragmentDetailBinding>() {
override fun onCreateBinding(inflater: LayoutInflater) = FragmentDetailBinding.inflate(inflater)
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
detailModel.setArtistUid(args.artistUid)
setupSelectionToolbar(binding.detailSelectionToolbar)
binding.detailToolbar.apply {
inflateMenu(R.menu.menu_genre_artist_detail)
@ -92,11 +92,14 @@ class ArtistDetailFragment : MenuFragment<FragmentDetailBinding>() {
// --- VIEWMODEL SETUP ---
detailModel.setArtistUid(args.artistUid)
collectImmediately(detailModel.currentArtist, ::updateItem)
collectImmediately(detailModel.artistData, detailAdapter::submitList)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(navModel.exploreNavigationItem, ::handleNavigation)
collectImmediately(selectionModel.selected, ::handleSelection)
}
override fun onDestroyBinding(binding: FragmentDetailBinding) {
@ -104,21 +107,21 @@ class ArtistDetailFragment : MenuFragment<FragmentDetailBinding>() {
binding.detailRecycler.adapter = null
}
private fun handleClick(item: Item) {
when (item) {
override fun onRealClick(music: Music) {
when (music) {
is Song -> {
when (settings.detailPlaybackMode) {
null ->
playbackModel.playFromArtist(
item, unlikelyToBeNull(detailModel.currentArtist.value))
MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
music, unlikelyToBeNull(detailModel.currentArtist.value))
MusicMode.SONGS -> playbackModel.playFromAll(music)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
else -> error("Unexpected playback mode: ${settings.detailPlaybackMode}")
}
}
is Album -> navModel.exploreNavigateTo(item)
else -> error("Unexpected datatype: ${item::class.simpleName}")
is Album -> navModel.exploreNavigateTo(music)
else -> error("Unexpected datatype: ${music::class.simpleName}")
}
}
@ -180,6 +183,20 @@ class ArtistDetailFragment : MenuFragment<FragmentDetailBinding>() {
requireBinding().detailToolbar.title = artist.resolveName(requireContext())
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
var item: Item? = null
if (parent is Album) {
item = parent
}
if (parent is Artist && parent == unlikelyToBeNull(detailModel.currentArtist.value)) {
item = song
}
detailAdapter.setPlayingItem(item, isPlaying)
}
private fun handleNavigation(item: Music?) {
val binding = requireBinding()
@ -210,17 +227,8 @@ class ArtistDetailFragment : MenuFragment<FragmentDetailBinding>() {
}
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
var item: Item? = null
if (parent is Album) {
item = parent
}
if (parent is Artist && parent == unlikelyToBeNull(detailModel.currentArtist.value)) {
item = song
}
detailAdapter.updateIndicator(item, isPlaying)
private fun handleSelection(selected: List<Music>) {
detailAdapter.setSelectedItems(selected)
requireBinding().detailSelectionToolbar.updateSelectionAmount(selected.size)
}
}

View file

@ -30,7 +30,7 @@ import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.detail.recycler.DetailAdapter
import org.oxycblt.auxio.detail.recycler.GenreDetailAdapter
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.MenuFragment
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -51,7 +51,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* A fragment that shows information for a particular [Genre].
* @author OxygenCobalt
*/
class GenreDetailFragment : MenuFragment<FragmentDetailBinding>() {
class GenreDetailFragment : ListFragment<FragmentDetailBinding>() {
private val detailModel: DetailViewModel by activityViewModels()
private val args: GenreDetailFragmentArgs by navArgs()
@ -62,10 +62,11 @@ class GenreDetailFragment : MenuFragment<FragmentDetailBinding>() {
DetailAdapter.Callback(
::handleClick,
::handleOpenItemMenu,
{},
::handleSelect,
::handlePlay,
::handleShuffle,
::handleOpenSortMenu))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
@ -77,7 +78,7 @@ class GenreDetailFragment : MenuFragment<FragmentDetailBinding>() {
override fun onCreateBinding(inflater: LayoutInflater) = FragmentDetailBinding.inflate(inflater)
override fun onBindingCreated(binding: FragmentDetailBinding, savedInstanceState: Bundle?) {
detailModel.setGenreUid(args.genreUid)
setupSelectionToolbar(binding.detailSelectionToolbar)
binding.detailToolbar.apply {
inflateMenu(R.menu.menu_genre_artist_detail)
@ -92,11 +93,14 @@ class GenreDetailFragment : MenuFragment<FragmentDetailBinding>() {
// --- VIEWMODEL SETUP ---
detailModel.setGenreUid(args.genreUid)
collectImmediately(detailModel.currentGenre, ::handleItemChange)
collectImmediately(detailModel.genreData, detailAdapter::submitList)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
collect(navModel.exploreNavigationItem, ::handleNavigation)
collectImmediately(selectionModel.selected, ::handleSelection)
}
override fun onDestroyBinding(binding: FragmentDetailBinding) {
@ -117,20 +121,20 @@ class GenreDetailFragment : MenuFragment<FragmentDetailBinding>() {
}
}
private fun handleClick(item: Item) {
when (item) {
is Artist -> navModel.exploreNavigateTo(item)
override fun onRealClick(music: Music) {
when (music) {
is Artist -> navModel.exploreNavigateTo(music)
is Song ->
when (settings.detailPlaybackMode) {
null ->
playbackModel.playFromGenre(
item, unlikelyToBeNull(detailModel.currentGenre.value))
MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
music, unlikelyToBeNull(detailModel.currentGenre.value))
MusicMode.SONGS -> playbackModel.playFromAll(music)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(music)
MusicMode.ARTISTS -> playbackModel.playFromArtist(music)
else -> error("Unexpected playback mode: ${settings.detailPlaybackMode}")
}
else -> error("Unexpected datatype: ${item::class.simpleName}")
else -> error("Unexpected datatype: ${music::class.simpleName}")
}
}
@ -177,6 +181,21 @@ class GenreDetailFragment : MenuFragment<FragmentDetailBinding>() {
requireBinding().detailToolbar.title = genre.resolveName(requireContext())
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
var item: Item? = null
if (parent is Artist) {
item = parent
}
if (parent is Genre && parent.uid == unlikelyToBeNull(detailModel.currentGenre.value).uid) {
item = song
}
detailAdapter.setPlayingItem(item, isPlaying)
}
private fun handleNavigation(item: Music?) {
when (item) {
is Song -> {
@ -201,17 +220,8 @@ class GenreDetailFragment : MenuFragment<FragmentDetailBinding>() {
}
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
var item: Item? = null
if (parent is Artist) {
item = parent
}
if (parent is Genre && parent.uid == unlikelyToBeNull(detailModel.currentGenre.value).uid) {
item = song
}
detailAdapter.updateIndicator(item, isPlaying)
private fun handleSelection(selected: List<Music>) {
detailAdapter.setSelectedItems(selected)
requireBinding().detailSelectionToolbar.updateSelectionAmount(selected.size)
}
}

View file

@ -28,7 +28,7 @@ import org.oxycblt.auxio.databinding.ItemDetailBinding
import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding
import org.oxycblt.auxio.detail.DiscHeader
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.recycler.PlayingIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SimpleItemCallback
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Song
@ -41,7 +41,7 @@ import org.oxycblt.auxio.util.inflater
* An adapter for displaying [Album] information and it's children.
* @author OxygenCobalt
*/
class AlbumDetailAdapter(private val callback: AlbumDetailAdapter.Callback) :
class AlbumDetailAdapter(private val callback: Callback) :
DetailAdapter(callback, DIFFER) {
override fun getItemViewType(position: Int) =
@ -180,7 +180,7 @@ class DiscHeaderViewHolder(private val binding: ItemDiscHeaderBinding) :
}
private class AlbumSongViewHolder private constructor(private val binding: ItemAlbumSongBinding) :
PlayingIndicatorAdapter.ViewHolder(binding.root) {
SelectionIndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Song, callback: AlbumDetailAdapter.Callback) {
// Hide the track number view if the song does not have a track.
if (item.track != null) {
@ -215,6 +215,10 @@ private class AlbumSongViewHolder private constructor(private val binding: ItemA
binding.songTrackBg.isPlaying = isPlaying
}
override fun updateSelectionIndicator(isSelected: Boolean) {
binding.root.isActivated = isSelected
}
companion object {
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_ALBUM_SONG

View file

@ -28,7 +28,9 @@ import org.oxycblt.auxio.databinding.ItemParentBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ItemMenuCallback
import org.oxycblt.auxio.list.ItemSelectCallback
import org.oxycblt.auxio.list.recycler.PlayingIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SimpleItemCallback
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
@ -153,14 +155,21 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
}
private class ArtistAlbumViewHolder private constructor(private val binding: ItemParentBinding) :
PlayingIndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Album, callback: ItemMenuCallback) {
SelectionIndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Album, callback: ItemSelectCallback) {
binding.parentImage.bind(item)
binding.parentName.text = item.resolveName(binding.context)
binding.parentInfo.text =
item.date?.resolveDate(binding.context) ?: binding.context.getString(R.string.def_date)
binding.parentMenu.setOnClickListener { callback.onOpenMenu(item, it) }
binding.root.setOnClickListener { callback.onClick(item) }
binding.root.apply {
setOnClickListener { callback.onClick(item) }
setOnLongClickListener {
callback.onSelect(item)
true
}
}
}
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
@ -168,6 +177,10 @@ private class ArtistAlbumViewHolder private constructor(private val binding: Ite
binding.parentImage.isPlaying = isPlaying
}
override fun updateSelectionIndicator(isSelected: Boolean) {
binding.root.isActivated = isSelected
}
companion object {
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_ARTIST_ALBUM
@ -183,13 +196,20 @@ private class ArtistAlbumViewHolder private constructor(private val binding: Ite
}
private class ArtistSongViewHolder private constructor(private val binding: ItemSongBinding) :
PlayingIndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Song, callback: ItemMenuCallback) {
SelectionIndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Song, callback: ItemSelectCallback) {
binding.songAlbumCover.bind(item)
binding.songName.text = item.resolveName(binding.context)
binding.songInfo.text = item.album.resolveName(binding.context)
binding.songMenu.setOnClickListener { callback.onOpenMenu(item, it) }
binding.root.setOnClickListener { callback.onClick(item) }
binding.root.apply {
setOnClickListener { callback.onClick(item) }
setOnLongClickListener {
callback.onSelect(item)
true
}
}
}
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
@ -197,6 +217,10 @@ private class ArtistSongViewHolder private constructor(private val binding: Item
binding.songAlbumCover.isPlaying = isPlaying
}
override fun updateSelectionIndicator(isSelected: Boolean) {
binding.root.isActivated = isSelected
}
companion object {
const val VIEW_TYPE = IntegerTable.VIEW_TYPE_ARTIST_SONG

View file

@ -29,17 +29,14 @@ import org.oxycblt.auxio.detail.SortHeader
import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ItemSelectCallback
import org.oxycblt.auxio.list.recycler.AuxioRecyclerView
import org.oxycblt.auxio.list.recycler.HeaderViewHolder
import org.oxycblt.auxio.list.recycler.PlayingIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SimpleItemCallback
import org.oxycblt.auxio.list.recycler.*
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.inflater
abstract class DetailAdapter(
private val callback: Callback,
diffCallback: DiffUtil.ItemCallback<Item>
) : PlayingIndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
) : SelectionIndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
@Suppress("LeakingThis") override fun getItemCount() = differ.currentList.size
override fun getItemViewType(position: Int) =

View file

@ -45,7 +45,8 @@ import org.oxycblt.auxio.home.list.AlbumListFragment
import org.oxycblt.auxio.home.list.ArtistListFragment
import org.oxycblt.auxio.home.list.GenreListFragment
import org.oxycblt.auxio.home.list.SongListFragment
import org.oxycblt.auxio.list.SelectionFragment
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.selection.SelectionToolbarOverlay
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -63,7 +64,7 @@ import org.oxycblt.auxio.util.*
* respective item.
* @author OxygenCobalt
*/
class HomeFragment : SelectionFragment<FragmentHomeBinding>() {
class HomeFragment : ListFragment<FragmentHomeBinding>() {
private val homeModel: HomeViewModel by androidActivityViewModels()
private val musicModel: MusicViewModel by activityViewModels()
@ -96,7 +97,7 @@ class HomeFragment : SelectionFragment<FragmentHomeBinding>() {
override fun onBindingCreated(binding: FragmentHomeBinding, savedInstanceState: Bundle?) {
binding.homeAppbar.addOnOffsetChangedListener { _, it -> handleAppBarAnimation(it) }
setupOverlay(binding.homeToolbarOverlay)
setupSelectionToolbar(binding.homeSelectionToolbar)
binding.homeToolbar.setOnMenuItemClickListener {
handleHomeMenuItem(it)
true
@ -171,7 +172,7 @@ class HomeFragment : SelectionFragment<FragmentHomeBinding>() {
val binding = requireBinding()
val range = binding.homeAppbar.totalScrollRange
binding.homeToolbarOverlay.alpha =
binding.homeSelectionToolbar.alpha =
1f - (abs(verticalOffset.toFloat()) / (range.toFloat() / 2))
binding.homeContent.updatePadding(
@ -182,8 +183,6 @@ class HomeFragment : SelectionFragment<FragmentHomeBinding>() {
when (item.itemId) {
R.id.action_search -> {
logD("Navigating to search")
// Reset selection (navigating to another selectable screen)
selectionModel.consume()
initAxisTransitions(MaterialSharedAxis.Z)
findNavController().navigate(HomeFragmentDirections.actionShowSearch())
}
@ -382,15 +381,13 @@ class HomeFragment : SelectionFragment<FragmentHomeBinding>() {
else -> return
}
// Reset selection (navigating to another selectable screen)
selectionModel.consume()
initAxisTransitions(MaterialSharedAxis.X)
findNavController().navigate(action)
}
private fun updateSelection(selected: List<Music>) {
val binding = requireBinding()
if (binding.homeToolbarOverlay.updateSelectionAmount(selected.size) &&
if (binding.homeSelectionToolbar.updateSelectionAmount(selected.size) &&
selected.isNotEmpty()) {
logD("Significant selection occurred, expanding AppBar")
// Significant enough change where we want to expand the RecyclerView
@ -409,7 +406,7 @@ class HomeFragment : SelectionFragment<FragmentHomeBinding>() {
}
private fun setupTabs(binding: FragmentHomeBinding) {
val toolbarParams = binding.homeToolbarOverlay.layoutParams as AppBarLayout.LayoutParams
val toolbarParams = binding.homeSelectionToolbar.layoutParams as AppBarLayout.LayoutParams
if (homeModel.tabs.size == 1) {
// A single tab makes the tab layout redundant, hide it and disable the collapsing
// behavior.

View file

@ -28,7 +28,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.list.*
import org.oxycblt.auxio.list.SelectionFragment
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.recycler.AlbumViewHolder
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SyncListDiffer
@ -45,7 +45,7 @@ import org.oxycblt.auxio.util.collectImmediately
* A [HomeListFragment] for showing a list of [Album]s.
* @author OxygenCobalt
*/
class AlbumListFragment : SelectionFragment<FragmentHomeListBinding>() {
class AlbumListFragment : ListFragment<FragmentHomeListBinding>() {
private val homeModel: HomeViewModel by activityViewModels()
private val homeAdapter =
@ -69,7 +69,7 @@ class AlbumListFragment : SelectionFragment<FragmentHomeListBinding>() {
}
collectImmediately(homeModel.albums, homeAdapter::replaceList)
collectImmediately(selectionModel.selected, homeAdapter::setSelected)
collectImmediately(selectionModel.selected, homeAdapter::setSelectedItems)
collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
}
@ -78,7 +78,7 @@ class AlbumListFragment : SelectionFragment<FragmentHomeListBinding>() {
binding.homeRecycler.adapter = null
}
override fun onClick(music: Music) {
override fun onRealClick(music: Music) {
check(music is Album) { "Unexpected datatype: ${music::class.java}" }
navModel.exploreNavigateTo(music)
}
@ -128,10 +128,10 @@ class AlbumListFragment : SelectionFragment<FragmentHomeListBinding>() {
}
private fun updatePlayback(parent: MusicParent?, isPlaying: Boolean) {
if (parent is Album) {
homeAdapter.updateIndicator(parent, isPlaying)
homeAdapter.setPlayingItem(parent, isPlaying)
} else {
// Ignore playback not from albums
homeAdapter.updateIndicator(null, isPlaying)
homeAdapter.setPlayingItem(null, isPlaying)
}
}

View file

@ -26,7 +26,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.list.*
import org.oxycblt.auxio.list.SelectionFragment
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.recycler.ArtistViewHolder
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SyncListDiffer
@ -43,7 +43,7 @@ import org.oxycblt.auxio.util.nonZeroOrNull
* A [HomeListFragment] for showing a list of [Artist]s.
* @author OxygenCobalt
*/
class ArtistListFragment : SelectionFragment<FragmentHomeListBinding>() {
class ArtistListFragment : ListFragment<FragmentHomeListBinding>() {
private val homeModel: HomeViewModel by activityViewModels()
private val homeAdapter =
@ -64,7 +64,7 @@ class ArtistListFragment : SelectionFragment<FragmentHomeListBinding>() {
}
collectImmediately(homeModel.artists, homeAdapter::replaceList)
collectImmediately(selectionModel.selected, homeAdapter::setSelected)
collectImmediately(selectionModel.selected, homeAdapter::setSelectedItems)
collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
}
@ -92,7 +92,7 @@ class ArtistListFragment : SelectionFragment<FragmentHomeListBinding>() {
}
}
override fun onClick(music: Music) {
override fun onRealClick(music: Music) {
check(music is Artist) { "Unexpected datatype: ${music::class.java}" }
navModel.exploreNavigateTo(music)
}
@ -104,10 +104,10 @@ class ArtistListFragment : SelectionFragment<FragmentHomeListBinding>() {
private fun updatePlayback(parent: MusicParent?, isPlaying: Boolean) {
if (parent is Artist) {
homeAdapter.updateIndicator(parent, isPlaying)
homeAdapter.setPlayingItem(parent, isPlaying)
} else {
// Ignore playback not from artists
homeAdapter.updateIndicator(null, isPlaying)
homeAdapter.setPlayingItem(null, isPlaying)
}
}

View file

@ -26,7 +26,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.list.*
import org.oxycblt.auxio.list.SelectionFragment
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.recycler.GenreViewHolder
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SyncListDiffer
@ -42,7 +42,7 @@ import org.oxycblt.auxio.util.collectImmediately
* A [HomeListFragment] for showing a list of [Genre]s.
* @author OxygenCobalt
*/
class GenreListFragment : SelectionFragment<FragmentHomeListBinding>() {
class GenreListFragment : ListFragment<FragmentHomeListBinding>() {
private val homeModel: HomeViewModel by activityViewModels()
private val homeAdapter =
@ -63,7 +63,7 @@ class GenreListFragment : SelectionFragment<FragmentHomeListBinding>() {
}
collectImmediately(homeModel.genres, homeAdapter::replaceList)
collectImmediately(selectionModel.selected, homeAdapter::setSelected)
collectImmediately(selectionModel.selected, homeAdapter::setSelectedItems)
collectImmediately(playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
}
@ -91,7 +91,7 @@ class GenreListFragment : SelectionFragment<FragmentHomeListBinding>() {
}
}
override fun onClick(music: Music) {
override fun onRealClick(music: Music) {
check(music is Genre) { "Unexpected datatype: ${music::class.java}" }
navModel.exploreNavigateTo(music)
}
@ -103,10 +103,10 @@ class GenreListFragment : SelectionFragment<FragmentHomeListBinding>() {
private fun updatePlayback(parent: MusicParent?, isPlaying: Boolean) {
if (parent is Genre) {
homeAdapter.updateIndicator(parent, isPlaying)
homeAdapter.setPlayingItem(parent, isPlaying)
} else {
// Ignore playback not from genres
homeAdapter.updateIndicator(null, isPlaying)
homeAdapter.setPlayingItem(null, isPlaying)
}
}

View file

@ -28,7 +28,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.list.*
import org.oxycblt.auxio.list.SelectionFragment
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.list.recycler.SelectionIndicatorAdapter
import org.oxycblt.auxio.list.recycler.SongViewHolder
import org.oxycblt.auxio.list.recycler.SyncListDiffer
@ -47,7 +47,7 @@ import org.oxycblt.auxio.util.context
* A [HomeListFragment] for showing a list of [Song]s.
* @author OxygenCobalt
*/
class SongListFragment : SelectionFragment<FragmentHomeListBinding>() {
class SongListFragment : ListFragment<FragmentHomeListBinding>() {
private val homeModel: HomeViewModel by activityViewModels()
private val homeAdapter =
@ -73,7 +73,7 @@ class SongListFragment : SelectionFragment<FragmentHomeListBinding>() {
}
collectImmediately(homeModel.songs, homeAdapter::replaceList)
collectImmediately(selectionModel.selected, homeAdapter::setSelected)
collectImmediately(selectionModel.selected, homeAdapter::setSelectedItems)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
}
@ -125,7 +125,7 @@ class SongListFragment : SelectionFragment<FragmentHomeListBinding>() {
}
}
override fun onClick(music: Music) {
override fun onRealClick(music: Music) {
check(music is Song) { "Unexpected datatype: ${music::class.java}" }
when (settings.libPlaybackMode) {
MusicMode.SONGS -> playbackModel.playFromAll(music)
@ -142,10 +142,10 @@ class SongListFragment : SelectionFragment<FragmentHomeListBinding>() {
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
if (parent == null) {
homeAdapter.updateIndicator(song, isPlaying)
homeAdapter.setPlayingItem(song, isPlaying)
} else {
// Ignore playback that is not from all songs
homeAdapter.updateIndicator(null, isPlaying)
homeAdapter.setPlayingItem(null, isPlaying)
}
}

View file

@ -39,18 +39,3 @@ open class ItemSelectCallback(
onOpenMenu: (Item, View) -> Unit,
val onSelect: (Item) -> Unit
) : ItemMenuCallback(onClick, onOpenMenu)
/** An interface for detecting if an item has been clicked once. */
interface ItemClickListener {
/** Called when an item is clicked once. */
fun onItemClick(item: Item)
}
/** An interface for detecting if an item has had it's menu opened. */
interface MenuItemListener : ItemClickListener {
/** Called when an item is long-clicked. */
fun onSelect(item: Item) {}
/** Called when an item desires to open a menu relating to it. */
fun onOpenMenu(item: Item, anchor: View)
}

View file

@ -17,6 +17,7 @@
package org.oxycblt.auxio.list
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.annotation.MenuRes
@ -25,10 +26,9 @@ import androidx.fragment.app.activityViewModels
import androidx.viewbinding.ViewBinding
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.list.selection.SelectionToolbarOverlay
import org.oxycblt.auxio.list.selection.SelectionViewModel
import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.shared.MainNavigationAction
import org.oxycblt.auxio.shared.NavigationViewModel
@ -37,17 +37,67 @@ import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
/**
* A fragment capable of creating menus. Automatically keeps track of and disposes of menus,
* preventing UI issues and memory leaks.
* @author OxygenCobalt
*/
abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() {
abstract class ListFragment<VB : ViewBinding> : ViewBindingFragment<VB>() {
protected val selectionModel: SelectionViewModel by activityViewModels()
private var currentMenu: PopupMenu? = null
protected val playbackModel: PlaybackViewModel by androidActivityViewModels()
protected val navModel: NavigationViewModel by activityViewModels()
override fun onDestroyBinding(binding: VB) {
super.onDestroyBinding(binding)
currentMenu?.dismiss()
currentMenu = null
}
fun setupSelectionToolbar(toolbar: SelectionToolbarOverlay) {
toolbar.apply {
setOnSelectionCancelListener { selectionModel.consume() }
setOnMenuItemClickListener {
handleSelectionMenuItem(it)
true
}
}
}
/** Handle a media item with a selection. */
private fun handleSelectionMenuItem(item: MenuItem) {
when (item.itemId) {
R.id.action_play_next -> {
playbackModel.playNext(selectionModel.consume())
requireContext().showToast(R.string.lng_queue_added)
}
R.id.action_queue_add -> {
playbackModel.addToQueue(selectionModel.consume())
requireContext().showToast(R.string.lng_queue_added)
}
}
}
/**
* Called when an item is clicked by the user and was not selected by [handleClick]. This can be
* optionally implemented if [handleClick] is used.
*/
open fun onRealClick(music: Music) {
throw NotImplementedError()
}
/** Provided implementation of an item click callback that handles selection. */
protected fun handleClick(item: Item) {
check(item is Music) { "Unexpected datatype: ${item::class.simpleName}" }
if (selectionModel.selected.value.isNotEmpty()) {
selectionModel.select(item)
} else {
onRealClick(item)
}
}
/** Provided implementation of an item selection callback. */
protected fun handleSelect(item: Item) {
check(item is Music) { "Unexpected datatype: ${item::class.simpleName}" }
selectionModel.select(item)
}
/**
* Opens the given menu in context of [song]. Assumes that the menu is only composed of common
* [Song] options.
@ -207,10 +257,4 @@ abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() {
show()
}
}
override fun onDestroyBinding(binding: T) {
super.onDestroyBinding(binding)
currentMenu?.dismiss()
currentMenu = null
}
}

View file

@ -1,70 +0,0 @@
/*
* 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.list
import android.view.MenuItem
import androidx.fragment.app.activityViewModels
import androidx.viewbinding.ViewBinding
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.util.showToast
abstract class SelectionFragment<VB : ViewBinding> : MenuFragment<VB>() {
protected val selectionModel: SelectionViewModel by activityViewModels()
open fun onClick(music: Music) {
throw NotImplementedError()
}
protected fun setupOverlay(overlay: SelectionToolbarOverlay) {
overlay.apply {
setOnSelectionCancelListener { selectionModel.consume() }
setOnMenuItemClickListener {
handleSelectionMenuItem(it)
true
}
}
}
private fun handleSelectionMenuItem(item: MenuItem) {
when (item.itemId) {
R.id.action_play_next -> {
playbackModel.playNext(selectionModel.consume())
requireContext().showToast(R.string.lng_queue_added)
}
R.id.action_queue_add -> {
playbackModel.addToQueue(selectionModel.consume())
requireContext().showToast(R.string.lng_queue_added)
}
}
}
protected fun handleClick(item: Item) {
check(item is Music) { "Unexpected datatype: ${item::class.simpleName}" }
if (selectionModel.selected.value.isNotEmpty()) {
selectionModel.select(item)
} else {
onClick(item)
}
}
protected fun handleSelect(item: Item) {
check(item is Music) { "Unexpected datatype: ${item::class.simpleName}" }
selectionModel.select(item)
}
}

View file

@ -44,7 +44,12 @@ abstract class PlayingIndicatorAdapter<VH : RecyclerView.ViewHolder> : RecyclerV
abstract val currentList: List<Item>
fun updateIndicator(item: Item?, isPlaying: Boolean) {
/**
* Update the currently playing item in the list.
* @param item The item being played, null if nothing is being played.
* @param isPlaying Whether playback is ongoing or paused.
*/
fun setPlayingItem(item: Item?, isPlaying: Boolean) {
var updatedItem = false
if (currentItem != item) {

View file

@ -37,7 +37,10 @@ abstract class SelectionIndicatorAdapter<VH : RecyclerView.ViewHolder> :
}
}
fun setSelected(items: List<Music>) {
/**
* Update the list of selected [items] within the adapter.
*/
fun setSelectedItems(items: List<Music>) {
val oldSelectedItems = selectedItems
val newSelectedItems = items.toSet()
if (newSelectedItems == oldSelectedItems) {

View file

@ -26,7 +26,6 @@ import org.oxycblt.auxio.databinding.ItemParentBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.list.Header
import org.oxycblt.auxio.list.ItemSelectCallback
import org.oxycblt.auxio.list.MenuItemListener
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -41,20 +40,6 @@ import org.oxycblt.auxio.util.inflater
*/
class SongViewHolder private constructor(private val binding: ItemSongBinding) :
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.apply {
setOnClickListener { listener.onItemClick(item) }
setOnLongClickListener {
listener.onSelect(item)
true
}
}
}
fun bind(item: Song, callback: ItemSelectCallback) {
binding.songAlbumCover.bind(item)
@ -100,21 +85,6 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
class AlbumViewHolder private constructor(private val binding: ItemParentBinding) :
SelectionIndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Album, listener: MenuItemListener) {
binding.parentImage.bind(item)
binding.parentName.text = item.resolveName(binding.context)
binding.parentInfo.text = item.resolveArtistContents(binding.context)
binding.parentMenu.setOnClickListener { listener.onOpenMenu(item, it) }
binding.root.apply {
setOnClickListener { listener.onItemClick(item) }
setOnLongClickListener {
listener.onSelect(item)
true
}
}
}
fun bind(item: Album, callback: ItemSelectCallback) {
binding.parentImage.bind(item)
binding.parentName.text = item.resolveName(binding.context)
@ -161,31 +131,6 @@ class AlbumViewHolder private constructor(private val binding: ItemParentBinding
class ArtistViewHolder private constructor(private val binding: ItemParentBinding) :
SelectionIndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Artist, listener: MenuItemListener) {
binding.parentImage.bind(item)
binding.parentName.text = item.resolveName(binding.context)
binding.parentInfo.text =
if (item.songs.isNotEmpty()) {
binding.context.getString(
R.string.fmt_two,
binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size),
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size))
} else {
// Artist has no songs, only display an album count.
binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size)
}
binding.parentMenu.setOnClickListener { listener.onOpenMenu(item, it) }
binding.root.apply {
setOnClickListener { listener.onItemClick(item) }
setOnLongClickListener {
listener.onSelect(item)
true
}
}
}
fun bind(item: Artist, callback: ItemSelectCallback) {
binding.parentImage.bind(item)
binding.parentName.text = item.resolveName(binding.context)
@ -242,25 +187,6 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
class GenreViewHolder private constructor(private val binding: ItemParentBinding) :
SelectionIndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Genre, listener: MenuItemListener) {
binding.parentImage.bind(item)
binding.parentName.text = item.resolveName(binding.context)
binding.parentInfo.text =
binding.context.getString(
R.string.fmt_two,
binding.context.getPlural(R.plurals.fmt_artist_count, item.artists.size),
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size))
binding.parentMenu.setOnClickListener { listener.onOpenMenu(item, it) }
binding.root.apply {
setOnClickListener { listener.onItemClick(item) }
setOnLongClickListener {
listener.onSelect(item)
true
}
}
}
fun bind(item: Genre, callback: ItemSelectCallback) {
binding.parentImage.bind(item)
binding.parentName.text = item.resolveName(binding.context)

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.list
package org.oxycblt.auxio.list.selection
import android.animation.ValueAnimator
import android.content.Context
@ -43,6 +43,10 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
MaterialToolbar(context).apply {
setNavigationIcon(R.drawable.ic_close_24)
inflateMenu(R.menu.menu_selection_actions)
if (isInEditMode) {
isInvisible = true
}
}
private var fadeThroughAnimator: ValueAnimator? = null

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.list
package org.oxycblt.auxio.list.selection
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
@ -46,9 +46,7 @@ class SelectionViewModel : ViewModel() {
}
/** Clear and return all selected items. */
fun consume(): List<Music> {
return _selected.value.also { _selected.value = listOf() }
}
fun consume() = _selected.value.also { _selected.value = listOf() }
override fun onCleared() {
super.onCleared()

View file

@ -32,7 +32,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSearchBinding
import org.oxycblt.auxio.list.Item
import org.oxycblt.auxio.list.ItemSelectCallback
import org.oxycblt.auxio.list.SelectionFragment
import org.oxycblt.auxio.list.ListFragment
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -48,7 +48,7 @@ import org.oxycblt.auxio.util.*
* better keyboard logic, recycler updating, and chips
* @author OxygenCobalt
*/
class SearchFragment : SelectionFragment<FragmentSearchBinding>() {
class SearchFragment : ListFragment<FragmentSearchBinding>() {
// SearchViewModel is only scoped to this Fragment
private val searchModel: SearchViewModel by androidViewModels()
@ -73,7 +73,7 @@ class SearchFragment : SelectionFragment<FragmentSearchBinding>() {
override fun onCreateBinding(inflater: LayoutInflater) = FragmentSearchBinding.inflate(inflater)
override fun onBindingCreated(binding: FragmentSearchBinding, savedInstanceState: Bundle?) {
setupOverlay(binding.searchToolbarOverlay)
setupSelectionToolbar(binding.searchSelectionToolbar)
binding.searchToolbar.apply {
val itemIdToSelect =
@ -124,7 +124,7 @@ class SearchFragment : SelectionFragment<FragmentSearchBinding>() {
binding.searchRecycler.adapter = null
}
override fun onClick(music: Music) {
override fun onRealClick(music: Music) {
when (music) {
is Song ->
when (settings.libPlaybackMode) {
@ -138,8 +138,6 @@ class SearchFragment : SelectionFragment<FragmentSearchBinding>() {
}
private fun handleSearchNavigateUp() {
// Reset selection (navigating to another selectable screen)
selectionModel.consume()
// Drop keyboard as it's no longer needed
imm.hide()
findNavController().navigateUp()
@ -176,7 +174,7 @@ class SearchFragment : SelectionFragment<FragmentSearchBinding>() {
}
private fun updatePlayback(song: Song?, parent: MusicParent?, isPlaying: Boolean) {
searchAdapter.updateIndicator(parent ?: song, isPlaying)
searchAdapter.setPlayingItem(parent ?: song, isPlaying)
}
private fun handleNavigation(item: Music?) {
@ -190,17 +188,13 @@ class SearchFragment : SelectionFragment<FragmentSearchBinding>() {
}
findNavController().navigate(action)
// Reset selection (navigating to another selectable screen)
selectionModel.consume()
// Drop keyboard as it's no longer needed
imm.hide()
}
private fun updateSelection(selected: List<Music>) {
searchAdapter.setSelected(selected)
if (requireBinding().searchToolbarOverlay.updateSelectionAmount(selected.size) &&
searchAdapter.setSelectedItems(selected)
if (requireBinding().searchSelectionToolbar.updateSelectionAmount(selected.size) &&
selected.isNotEmpty()) {
imm.hide()
}

View file

@ -13,12 +13,20 @@
app:liftOnScroll="true"
app:liftOnScrollTargetViewId="@id/detail_recycler">
<org.oxycblt.auxio.list.selection.SelectionToolbarOverlay
android:id="@+id/detail_selection_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/detail_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationIcon="@drawable/ic_back_24" />
</org.oxycblt.auxio.list.selection.SelectionToolbarOverlay>
</org.oxycblt.auxio.detail.DetailAppBarLayout>
<org.oxycblt.auxio.list.recycler.AuxioRecyclerView

View file

@ -13,8 +13,8 @@
style="@style/Widget.Auxio.AppBarLayout"
android:fitsSystemWindows="true">
<org.oxycblt.auxio.list.SelectionToolbarOverlay
android:id="@+id/home_toolbar_overlay"
<org.oxycblt.auxio.list.selection.SelectionToolbarOverlay
android:id="@+id/home_selection_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -26,7 +26,7 @@
app:menu="@menu/menu_home"
app:title="@string/info_app_name" />
</org.oxycblt.auxio.list.SelectionToolbarOverlay>
</org.oxycblt.auxio.list.selection.SelectionToolbarOverlay>
<com.google.android.material.tabs.TabLayout
android:id="@+id/home_tabs"

View file

@ -12,8 +12,8 @@
app:liftOnScroll="true"
app:liftOnScrollTargetViewId="@id/search_recycler">
<org.oxycblt.auxio.list.SelectionToolbarOverlay
android:id="@+id/search_toolbar_overlay"
<org.oxycblt.auxio.list.selection.SelectionToolbarOverlay
android:id="@+id/search_selection_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -49,7 +49,7 @@
</com.google.android.material.appbar.MaterialToolbar>
</org.oxycblt.auxio.list.SelectionToolbarOverlay>
</org.oxycblt.auxio.list.selection.SelectionToolbarOverlay>
</org.oxycblt.auxio.shared.AuxioAppBarLayout>