home: add selection actions

Implement actions for selections in the home view.

Play selected and shuffle selected have been removed for now until the
queue can be properly reworked
This commit is contained in:
Alexander Capehart 2022-12-16 13:35:48 -07:00
parent 04e25eb90a
commit f3365fc40b
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
6 changed files with 87 additions and 31 deletions

View file

@ -60,12 +60,7 @@ import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.SelectionViewModel import org.oxycblt.auxio.ui.SelectionViewModel
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.*
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.getColorCompat
import org.oxycblt.auxio.util.lazyReflectedField
import org.oxycblt.auxio.util.logD
/** /**
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each * The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each
@ -118,6 +113,11 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
} }
} }
binding.homeToolbarOverlay.registerListeners(
onExit = { selectionModel.consume() },
onMenuItemClick = this
)
binding.homeToolbar.setOnMenuItemClickListener(this@HomeFragment) binding.homeToolbar.setOnMenuItemClickListener(this@HomeFragment)
updateTabConfiguration() updateTabConfiguration()
@ -175,11 +175,14 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
override fun onDestroyBinding(binding: FragmentHomeBinding) { override fun onDestroyBinding(binding: FragmentHomeBinding) {
super.onDestroyBinding(binding) super.onDestroyBinding(binding)
binding.homeToolbarOverlay.unregisterListeners()
binding.homeToolbar.setOnMenuItemClickListener(null) binding.homeToolbar.setOnMenuItemClickListener(null)
} }
override fun onMenuItemClick(item: MenuItem): Boolean { override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
// HOME
R.id.action_search -> { R.id.action_search -> {
logD("Navigating to search") logD("Navigating to search")
initAxisTransitions(MaterialSharedAxis.Z) initAxisTransitions(MaterialSharedAxis.Z)
@ -205,6 +208,19 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
.getSortForTab(homeModel.currentTab.value) .getSortForTab(homeModel.currentTab.value)
.withAscending(item.isChecked)) .withAscending(item.isChecked))
} }
// SELECTION
R.id.action_play_next_selection -> {
playbackModel.playNext(selectionModel.consume())
requireContext().showToast(R.string.lng_queue_added)
}
R.id.action_queue_add_selection -> {
playbackModel.addToQueue(selectionModel.consume())
requireContext().showToast(R.string.lng_queue_added)
}
else -> { else -> {
// Sorting option was selected, mark it as selected and update the mode // Sorting option was selected, mark it as selected and update the mode
item.isChecked = true item.isChecked = true

View file

@ -25,11 +25,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.InternalPlayer import org.oxycblt.auxio.playback.state.InternalPlayer
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
@ -44,6 +40,8 @@ import org.oxycblt.auxio.util.application
* an interface that properly sanitizes input and abstracts functions unlike the master class.** * an interface that properly sanitizes input and abstracts functions unlike the master class.**
* *
* @author OxygenCobalt * @author OxygenCobalt
*
* TODO: Queue additions without a song should map to playing selected
*/ */
class PlaybackViewModel(application: Application) : class PlaybackViewModel(application: Application) :
AndroidViewModel(application), PlaybackStateManager.Callback { AndroidViewModel(application), PlaybackStateManager.Callback {
@ -217,6 +215,11 @@ class PlaybackViewModel(application: Application) :
playbackManager.playNext(settings.detailGenreSort.songs(genre.songs)) playbackManager.playNext(settings.detailGenreSort.songs(genre.songs))
} }
/** Add a selection [selection] to the top of the queue. */
fun playNext(selection: List<Music>) {
playbackManager.playNext(selectionToSongs(selection))
}
/** Add a [Song] to the end of the queue. */ /** Add a [Song] to the end of the queue. */
fun addToQueue(song: Song) { fun addToQueue(song: Song) {
playbackManager.addToQueue(song) playbackManager.addToQueue(song)
@ -237,6 +240,22 @@ class PlaybackViewModel(application: Application) :
playbackManager.addToQueue(settings.detailGenreSort.songs(genre.songs)) playbackManager.addToQueue(settings.detailGenreSort.songs(genre.songs))
} }
/** Add a selection [selection] to the top of the queue. */
fun addToQueue(selection: List<Music>) {
playbackManager.addToQueue(selectionToSongs(selection))
}
private fun selectionToSongs(selection: List<Music>): List<Song> {
return selection.flatMap {
when (it) {
is Album -> settings.detailAlbumSort.songs(it.songs)
is Artist -> settings.detailArtistSort.songs(it.songs)
is Genre -> settings.detailGenreSort.songs(it.songs)
is Song -> listOf(it)
}
}
}
// --- STATUS FUNCTIONS --- // --- STATUS FUNCTIONS ---
/** Flip the playing status, e.g from playing to paused */ /** Flip the playing status, e.g from playing to paused */

View file

@ -3,6 +3,7 @@ package org.oxycblt.auxio.ui
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
@ -30,6 +31,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
setNavigationIcon(R.drawable.ic_close_24) setNavigationIcon(R.drawable.ic_close_24)
} }
private val selectionMenu = selectionToolbar.menu
private var fadeThroughAnimator: ValueAnimator? = null private var fadeThroughAnimator: ValueAnimator? = null
override fun onFinishInflate() { override fun onFinishInflate() {
@ -45,9 +48,22 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Add listeners for the selection toolbar. * Add listeners for the selection toolbar.
*/ */
fun setListeners(onExit: Toolbar.OnMenuItemClickListener, fun registerListeners(onExit: OnClickListener,
onMenuItemClick: Toolbar.OnMenuItemClickListener) { onMenuItemClick: Toolbar.OnMenuItemClickListener) {
// TODO: Sub selectionToolbar.apply {
setNavigationOnClickListener(onExit)
setOnMenuItemClickListener(onMenuItemClick)
}
}
/**
* Unregister listeners for this instance.
*/
fun unregisterListeners() {
selectionToolbar.apply {
setNavigationOnClickListener(null)
setOnMenuItemClickListener(null)
}
} }
/** /**
@ -69,13 +85,16 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
// don't work due to translation) // don't work due to translation)
val targetInnerAlpha: Float val targetInnerAlpha: Float
val targetSelectionAlpha: Float val targetSelectionAlpha: Float
val targetDuration: Long
if (selectionVisible) { if (selectionVisible) {
targetInnerAlpha = 0f targetInnerAlpha = 0f
targetSelectionAlpha = 1f targetSelectionAlpha = 1f
targetDuration = context.resources.getInteger(R.integer.anim_fade_enter_duration).toLong()
} else { } else {
targetInnerAlpha = 1f targetInnerAlpha = 1f
targetSelectionAlpha = 0f targetSelectionAlpha = 0f
targetDuration = context.resources.getInteger(R.integer.anim_fade_exit_duration).toLong()
} }
if (innerToolbar.alpha == targetInnerAlpha && if (innerToolbar.alpha == targetInnerAlpha &&
@ -94,7 +113,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
} }
fadeThroughAnimator = ValueAnimator.ofFloat(innerToolbar.alpha, targetInnerAlpha).apply { fadeThroughAnimator = ValueAnimator.ofFloat(innerToolbar.alpha, targetInnerAlpha).apply {
duration = context.resources.getInteger(R.integer.anim_fade_enter_duration).toLong() duration = targetDuration
addUpdateListener { addUpdateListener {
changeToolbarAlpha(it.animatedValue as Float) changeToolbarAlpha(it.animatedValue as Float)
} }

View file

@ -40,4 +40,11 @@ class SelectionViewModel : ViewModel() {
_selected.value = items _selected.value = items
} }
} }
/** Clear and return all selected items. */
fun consume(): List<Music> {
return _selected.value.also {
_selected.value = listOf()
}
}
} }

View file

@ -185,12 +185,6 @@ abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() {
private fun musicMenuImpl(anchor: View, @MenuRes menuRes: Int, onSelect: (Int) -> Boolean) { private fun musicMenuImpl(anchor: View, @MenuRes menuRes: Int, onSelect: (Int) -> Boolean) {
menu(anchor, menuRes) { menu(anchor, menuRes) {
for (item in menu.children) {
if (item.itemId == R.id.action_play_next || item.itemId == R.id.action_queue_add) {
item.isEnabled = playbackModel.song.value != null
}
}
setOnMenuItemClickListener { item -> onSelect(item.itemId) } setOnMenuItemClickListener { item -> onSelect(item.itemId) }
} }
} }

View file

@ -2,20 +2,21 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_play_next" android:id="@+id/action_play_next_selection"
android:title="@string/lbl_play_next" android:title="@string/lbl_play_next"
android:icon="@drawable/ic_play_next_24" android:icon="@drawable/ic_play_next_24"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/action_queue_add" android:id="@+id/action_queue_add_selection"
android:title="@string/lbl_queue_add" android:title="@string/lbl_queue_add"
app:showAsAction="never" /> app:showAsAction="never" />
<item <!-- TOOD: Disabled until able to get queue system into shape -->
android:id="@+id/action_play" <!-- <item-->
android:title="@string/lbl_play_selected" <!-- android:id="@+id/action_play_selection"-->
app:showAsAction="never"/> <!-- android:title="@string/lbl_play_selected"-->
<item <!-- app:showAsAction="never"/>-->
android:id="@+id/action_shuffle" <!-- <item-->
android:title="@string/lbl_shuffle_selected" <!-- android:id="@+id/action_shuffle_selection"-->
app:showAsAction="never"/> <!-- android:title="@string/lbl_shuffle_selected"-->
<!-- app:showAsAction="never"/>-->
</menu> </menu>