playback: add elevation to queue drag
Add a nice elevation effect to queue dragging operations. This has no purpose outside of looking nicer. Luckily it doesn't effect queue behavior at all.
This commit is contained in:
parent
504e8260ac
commit
9aa2c99be4
9 changed files with 74 additions and 13 deletions
|
@ -49,6 +49,8 @@ class MainFragment : Fragment() {
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
|
playbackModel.setupPlayback(requireContext())
|
||||||
|
|
||||||
// Change CompactPlaybackFragment's visibility here so that an animation occurs.
|
// Change CompactPlaybackFragment's visibility here so that an animation occurs.
|
||||||
handleCompactPlaybackVisibility(binding, playbackModel.song.value)
|
handleCompactPlaybackVisibility(binding, playbackModel.song.value)
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ import org.oxycblt.auxio.databinding.DialogAccentBinding
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.resolveColor
|
import org.oxycblt.auxio.resolveColor
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.settings.ui.LifecycleDialog
|
import org.oxycblt.auxio.ui.LifecycleDialog
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog responsible for showing the list of accents to select.
|
* Dialog responsible for showing the list of accents to select.
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.oxycblt.auxio.settings.SettingsManager
|
||||||
/**
|
/**
|
||||||
* ViewModel that stores data for the [DetailFragment]s, such as what they're showing & what
|
* ViewModel that stores data for the [DetailFragment]s, such as what they're showing & what
|
||||||
* [SortMode] they are currently on.
|
* [SortMode] they are currently on.
|
||||||
|
* TODO: Redo sorting here
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class DetailViewModel : ViewModel() {
|
class DetailViewModel : ViewModel() {
|
||||||
|
|
|
@ -36,8 +36,8 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogExcludedBinding
|
import org.oxycblt.auxio.databinding.DialogExcludedBinding
|
||||||
import org.oxycblt.auxio.logD
|
import org.oxycblt.auxio.logD
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.settings.ui.LifecycleDialog
|
|
||||||
import org.oxycblt.auxio.showToast
|
import org.oxycblt.auxio.showToast
|
||||||
|
import org.oxycblt.auxio.ui.LifecycleDialog
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -46,8 +46,11 @@ import org.oxycblt.auxio.recycler.DisplayMode
|
||||||
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
|
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail
|
||||||
* views for each respective fragment.
|
* views for each respective fragment.
|
||||||
* TODO: Re-add sorting (but new and improved)
|
* TODO: Re-add sorting (but new and improved)
|
||||||
|
* It will require a new SortMode to be made simply for compat. Migrate the old SortMode
|
||||||
|
* eventually.
|
||||||
* TODO: Add lift-on-scroll eventually [when I can file a bug report or hack it into working]
|
* TODO: Add lift-on-scroll eventually [when I can file a bug report or hack it into working]
|
||||||
* FIXME: Keep the collapsed state in the ViewModel so we can make sure it stays consistent
|
* FIXME: Find a way to store the collapsed state so it stays consistent
|
||||||
|
* TODO: Fix issue where TabLayout ripples will shove above the indicator
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
|
@ -93,7 +96,6 @@ class HomeFragment : Fragment() {
|
||||||
// internal recyclerview and change the touch slope so that touch actions will
|
// internal recyclerview and change the touch slope so that touch actions will
|
||||||
// act more as a scroll than as a swipe.
|
// act more as a scroll than as a swipe.
|
||||||
// Derived from: https://al-e-shevelev.medium.com/how-to-reduce-scroll-sensitivity-of-viewpager2-widget-87797ad02414
|
// Derived from: https://al-e-shevelev.medium.com/how-to-reduce-scroll-sensitivity-of-viewpager2-widget-87797ad02414
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val recycler = ViewPager2::class.java.getDeclaredField("mRecyclerView").run {
|
val recycler = ViewPager2::class.java.getDeclaredField("mRecyclerView").run {
|
||||||
isAccessible = true
|
isAccessible = true
|
||||||
|
@ -125,8 +127,6 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
// --- VIEWMODEL SETUP ---
|
// --- VIEWMODEL SETUP ---
|
||||||
|
|
||||||
playbackModel.setupPlayback(requireContext())
|
|
||||||
|
|
||||||
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
detailModel.navToItem.observe(viewLifecycleOwner) { item ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is Song -> findNavController().navigate(
|
is Song -> findNavController().navigate(
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.oxycblt.auxio.music.Genre
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.recycler.DisplayMode
|
import org.oxycblt.auxio.recycler.DisplayMode
|
||||||
|
import org.oxycblt.auxio.recycler.sliceArticle
|
||||||
import org.oxycblt.auxio.spans
|
import org.oxycblt.auxio.spans
|
||||||
import org.oxycblt.auxio.ui.newMenu
|
import org.oxycblt.auxio.ui.newMenu
|
||||||
|
|
||||||
|
@ -103,7 +104,13 @@ class HomeListFragment : Fragment() {
|
||||||
homeAdapter.updateData(toObserve.value!!)
|
homeAdapter.updateData(toObserve.value!!)
|
||||||
|
|
||||||
toObserve.observe(viewLifecycleOwner) { data ->
|
toObserve.observe(viewLifecycleOwner) { data ->
|
||||||
homeAdapter.updateData(data)
|
homeAdapter.updateData(
|
||||||
|
data.sortedWith(
|
||||||
|
compareBy(String.CASE_INSENSITIVE_ORDER) {
|
||||||
|
it.name.sliceArticle()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
logD("Fragment created")
|
logD("Fragment created")
|
||||||
|
|
|
@ -18,8 +18,11 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback.queue
|
package org.oxycblt.auxio.playback.queue
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
@ -30,14 +33,10 @@ import kotlin.math.sign
|
||||||
* The Drag callback used by the queue recyclerview. Delivers updates to [PlaybackViewModel]
|
* The Drag callback used by the queue recyclerview. Delivers updates to [PlaybackViewModel]
|
||||||
* and [QueueAdapter] simultaneously.
|
* and [QueueAdapter] simultaneously.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
* TODO: Its possible to apply some elevation to the item views when they are picked up,
|
|
||||||
* however you need to keep track of the viewholder in the item touch helper and reset
|
|
||||||
* it when done. Theoretically this also means you can do the material drawer thing where
|
|
||||||
* the bottom of the recyclerview a darker color but is only shown when an item is moved.
|
|
||||||
* Maybe.
|
|
||||||
*/
|
*/
|
||||||
class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouchHelper.Callback() {
|
class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouchHelper.Callback() {
|
||||||
private lateinit var queueAdapter: QueueAdapter
|
private lateinit var queueAdapter: QueueAdapter
|
||||||
|
private var shouldLift = true
|
||||||
|
|
||||||
override fun getMovementFlags(
|
override fun getMovementFlags(
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
|
@ -87,6 +86,57 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onChildDraw(
|
||||||
|
c: Canvas,
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
dX: Float,
|
||||||
|
dY: Float,
|
||||||
|
actionState: Int,
|
||||||
|
isCurrentlyActive: Boolean
|
||||||
|
) {
|
||||||
|
// The material design page on elevation has a cool example of draggable items elevating
|
||||||
|
// themselves when being dragged. Too bad google doesn't provide a single utility to do
|
||||||
|
// this in your own app :^). To emulate it, I check if this child is in a drag state and
|
||||||
|
// then animate an elevation change. This animation also changes the background so that
|
||||||
|
// the item will actually draw over.
|
||||||
|
|
||||||
|
val view = viewHolder.itemView
|
||||||
|
|
||||||
|
if (shouldLift && isCurrentlyActive && actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
|
||||||
|
view.animate()
|
||||||
|
.withStartAction { view.setBackgroundResource(R.color.surface) }
|
||||||
|
.translationZ(view.resources.getDimension(R.dimen.elevation_normal))
|
||||||
|
.setDuration(100)
|
||||||
|
.setInterpolator(AccelerateDecelerateInterpolator())
|
||||||
|
.start()
|
||||||
|
|
||||||
|
shouldLift = false
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
|
||||||
|
// When an elevated item is done, we reset the elevation using another animation
|
||||||
|
// and set the background to null again so a seam doesn't show up in further actions.
|
||||||
|
|
||||||
|
val view = viewHolder.itemView
|
||||||
|
|
||||||
|
if (view.translationZ != 0.0f) {
|
||||||
|
viewHolder.itemView.animate()
|
||||||
|
.withEndAction { view.setBackgroundResource(android.R.color.transparent) }
|
||||||
|
.translationZ(0.0f)
|
||||||
|
.setDuration(100)
|
||||||
|
.setInterpolator(AccelerateDecelerateInterpolator())
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldLift = true
|
||||||
|
|
||||||
|
super.clearView(recyclerView, viewHolder)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition, queueAdapter)
|
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition, queueAdapter)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
package org.oxycblt.auxio.settings.ui
|
package org.oxycblt.auxio.settings.ui
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import org.oxycblt.auxio.ui.LifecycleDialog
|
||||||
|
|
||||||
class IntListPrefDialog(private val pref: IntListPreference) : LifecycleDialog() {
|
class IntListPrefDialog(private val pref: IntListPreference) : LifecycleDialog() {
|
||||||
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.settings.ui
|
package org.oxycblt.auxio.ui
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
Loading…
Reference in a new issue