diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 8713de33c..505e8aff9 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -60,7 +60,6 @@ import org.oxycblt.auxio.util.makeScrollingViewFade * - Edge-to-edge is borked still, unsure how to really fix this aside from making some * magic layout like Material Files, but even then it might not work since the scrolling * views are not laid side-by-side to the layout itself. - * So excited to have enough time to get to these in like...november. * @author OxygenCobalt */ class HomeFragment : Fragment() { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt index b38eac27a..22d949684 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -20,6 +20,7 @@ package org.oxycblt.auxio.playback.queue import android.annotation.SuppressLint import android.view.MotionEvent +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.ItemTouchHelper @@ -35,6 +36,7 @@ import org.oxycblt.auxio.ui.DiffCallback import org.oxycblt.auxio.ui.HeaderViewHolder import org.oxycblt.auxio.util.applyMaterialDrawable import org.oxycblt.auxio.util.inflater +import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE /** @@ -113,6 +115,8 @@ class QueueAdapter( fun removeItem(adapterIndex: Int) { data.removeAt(adapterIndex) + logD(data) + /* * If the data from the next queue is now entirely empty [Signified by a header at the * end, remove the next queue header as notify as such. @@ -123,14 +127,15 @@ class QueueAdapter( * Otherwise just remove the item as usual. */ if (data[data.lastIndex] is Header) { + logD("Queue is empty, removing header") + val lastIndex = data.lastIndex - data.removeAt(lastIndex) - notifyItemRangeRemoved(lastIndex, 2) - } else if (data.lastIndex >= 1 && data[0] is Header && data[1] is Header) { - data.removeAt(0) + } else if (data.lastIndex >= 1 && data[0] is ActionHeader && data[1] is Header) { + logD("User queue is empty, removing header") + data.removeAt(0) notifyItemRangeRemoved(0, 2) } else { notifyItemRemoved(adapterIndex) @@ -143,9 +148,11 @@ class QueueAdapter( inner class QueueSongViewHolder( private val binding: ItemQueueSongBinding, ) : BaseViewHolder(binding) { + val bodyView: View get() = binding.body + val backgroundView: View get() = binding.background init { - binding.root.applyMaterialDrawable() + binding.body.applyMaterialDrawable() } @SuppressLint("ClickableViewAccessibility") diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt index 97783d7ac..ceee98a1a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueDragCallback.kt @@ -20,6 +20,7 @@ package org.oxycblt.auxio.playback.queue import android.graphics.Canvas import android.view.animation.AccelerateDecelerateInterpolator +import androidx.core.view.isInvisible import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.google.android.material.shape.MaterialShapeDrawable @@ -31,8 +32,9 @@ import kotlin.math.min import kotlin.math.sign /** - * A highly customized [ItemTouchHelper.Callback] that handles queue item moving, removal, and some - * of the UI magic that makes up the queue UI. + * A highly customized [ItemTouchHelper.Callback] that handles the queue system while basically + * rebuilding most the "Material-y" aspects of an editable list because Google's implementations + * are hot garbage. This shouldn't have *too many* UI bugs. I hope. * @author OxygenCobalt */ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouchHelper.Callback() { @@ -88,48 +90,63 @@ class QueueDragCallback(private val playbackModel: PlaybackViewModel) : ItemTouc // themselves when being dragged. Too bad google's implementation of this doesn't even // work! To emulate it on my own, I check if this child is in a drag state and then animate // an elevation change. - // TODO: Maybe restrict the item from being drawn over the recycler bounds? - // Seems like its possible with enough UI magic - // TODO: Add an accented BG to the removal action + // TODO: Some other enhancements I could make maybe + // - Maybe stopping dragged items from extending beyond their specific part of the queue? - val view = viewHolder.itemView + val holder = viewHolder as QueueAdapter.QueueSongViewHolder if (shouldLift && isCurrentlyActive && actionState == ItemTouchHelper.ACTION_STATE_DRAG) { - val bg = view.background as MaterialShapeDrawable + val bg = holder.bodyView.background as MaterialShapeDrawable + val elevation = recyclerView.resources.getDimension(R.dimen.elevation_small) - view.animate() - .translationZ(view.resources.getDimension(R.dimen.elevation_small)) + holder.itemView.animate() + .translationZ(elevation) .setDuration(100) - .setUpdateListener { bg.elevation = view.translationZ } + .setUpdateListener { + bg.elevation = holder.itemView.translationZ + } .setInterpolator(AccelerateDecelerateInterpolator()) .start() shouldLift = false } - view.translationX = dX - view.translationY = dY + // We show a background with a clear icon behind the queue song each time one is swiped + // away. To avoid any canvas shenanigans, we just place a custom background view behind the + // main "body" layout of the queue item and then translate that. + // + // That comes with a couple of problems, however. For one, the background view will always + // lag behind the body view, resulting in a noticeable pixel offset when dragging. To fix + // this, we make this a separate view and make this view invisible whenever the item is + // not being swiped. We cannot merge this view with the FrameLayout, as that will cause + // another weird pixel desync issue that is less visible but still incredibly annoying. + if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { + holder.backgroundView.isInvisible = dX == 0f + } + + holder.bodyView.translationX = dX + holder.itemView.translationY = dY } override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { // When an elevated item is cleared, we reset the elevation using another animation. - val view = viewHolder.itemView + val holder = viewHolder as QueueAdapter.QueueSongViewHolder - if (view.translationZ != 0.0f) { - val bg = view.background as MaterialShapeDrawable + if (holder.itemView.translationZ != 0.0f) { + val bg = holder.bodyView.background as MaterialShapeDrawable - view.animate() + holder.itemView.animate() .translationZ(0.0f) .setDuration(100) - .setUpdateListener { bg.elevation = view.translationZ } + .setUpdateListener { bg.elevation = holder.itemView.translationZ } .setInterpolator(AccelerateDecelerateInterpolator()) .start() } shouldLift = true - view.translationX = 0f - view.translationY = 0f + holder.bodyView.translationX = 0f + holder.itemView.translationY = 0f } override fun onMove( diff --git a/app/src/main/java/org/oxycblt/auxio/ui/LiftAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/LiftAppBarLayout.kt index 6e69d2bda..327255026 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/LiftAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/LiftAppBarLayout.kt @@ -34,8 +34,6 @@ import org.oxycblt.auxio.util.logE * will not properly respond to RecyclerView events. * **Note:** This layout relies on [AppBarLayout.liftOnScrollTargetViewId] to figure out what * scrolling view to use. Failure to specify this will result in the layout not working. - * FIXME: Fix issue where elevation change will always animate - * FIXME: Fix issue where expanded state does not work correctly when switching orientations */ class LiftAppBarLayout @JvmOverloads constructor( context: Context, diff --git a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt index 82f69f8f8..f0aa06c80 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt @@ -30,6 +30,7 @@ import android.view.WindowInsets import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -111,6 +112,15 @@ fun @receiver:ColorRes Int.resolveColor(context: Context): Int { fun @receiver:ColorRes Int.resolveStateList(context: Context) = ContextCompat.getColorStateList(context, this) +/* + * Resolve a color and turn it into a [ColorStateList] + * @param context [Context] required + * @return The resolved color as a [ColorStateList] + * @see resolveColor + */ +fun @receiver:DrawableRes Int.resolveDrawable(context: Context) = + requireNotNull(ContextCompat.getDrawable(context, this)) + /** * Resolve this int into a color as if it was an attribute */ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index d9ea34809..140afd53b 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -6,5 +6,6 @@ android:viewportHeight="108"> diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 5ec2a9843..38f46f701 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -6,7 +6,7 @@ android:viewportHeight="108"> diff --git a/app/src/main/res/layout/item_queue_song.xml b/app/src/main/res/layout/item_queue_song.xml index ea8b27042..8ecfda84c 100644 --- a/app/src/main/res/layout/item_queue_song.xml +++ b/app/src/main/res/layout/item_queue_song.xml @@ -11,65 +11,88 @@ type="org.oxycblt.auxio.music.Song" /> - - - - - - + + android:src="@drawable/ic_clear" + android:layout_gravity="end|center_vertical" + android:padding="@dimen/spacing_medium" + app:tint="?attr/colorSurface" + android:contentDescription="@string/desc_clear_queue_item"/> - + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a8eb0e732..ea94169e0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,6 +119,7 @@ Turn shuffle on or off Clear queue + Remove this queue item Move queue song Clear search query Remove excluded directory diff --git a/build.gradle b/build.gradle index 18e31705b..8448e9125 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.android.tools.build:gradle:7.0.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"