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:
parent
04e25eb90a
commit
f3365fc40b
6 changed files with 87 additions and 31 deletions
|
@ -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
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue