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:
OxygenCobalt 2021-08-23 06:49:03 -06:00
parent 504e8260ac
commit 9aa2c99be4
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
9 changed files with 74 additions and 13 deletions

View file

@ -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)

View file

@ -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.

View file

@ -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() {

View file

@ -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
/** /**

View file

@ -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(

View file

@ -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")

View file

@ -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)
} }

View file

@ -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) {

View file

@ -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