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 7aa567279..6036fab3c 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,8 +20,10 @@ package org.oxycblt.auxio.playback.queue import android.graphics.Canvas import android.view.animation.AccelerateDecelerateInterpolator +import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.appbar.AppBarLayout import org.oxycblt.auxio.R import org.oxycblt.auxio.playback.PlaybackViewModel import kotlin.math.abs @@ -32,15 +34,15 @@ import kotlin.math.sign /** * The Drag callback used by the queue recyclerview. Delivers updates to [PlaybackViewModel] * and [QueueAdapter] simultaneously. - * @param onItemScroll Callback for when an item begins to scroll off-screen. Argument passed - * is the dY value. * @author OxygenCobalt */ class QueueDragCallback( private val playbackModel: PlaybackViewModel, - private val onItemScroll: (Int) -> Unit + private val coordinator: CoordinatorLayout, + private val appBar: AppBarLayout ) : ItemTouchHelper.Callback() { private lateinit var queueAdapter: QueueAdapter + private val tConsumed = IntArray(2) private var shouldLift = true override fun getMovementFlags( @@ -79,24 +81,18 @@ class QueueDragCallback( val result = clampedAbsVelocity * sign(viewSizeOutOfBounds.toDouble()).toInt() recyclerView.post { - onItemScroll(result) + // CoordinatorLayout refuses to propagate a scroll event initiated by an item scroll, + // so we do it ourselves. + (appBar.layoutParams as CoordinatorLayout.LayoutParams).behavior + ?.onNestedPreScroll( + coordinator, appBar, recyclerView, + 0, result, tConsumed, 0 + ) } return result } - override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder - ): Boolean { - return playbackModel.moveQueueDataItems( - viewHolder.bindingAdapterPosition, - target.bindingAdapterPosition, - queueAdapter - ) - } - override fun onChildDraw( c: Canvas, recyclerView: RecyclerView, @@ -107,10 +103,12 @@ class QueueDragCallback( 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 + // 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. This animation also changes the background so that // the item will actually draw over. + // TODO: Maybe restrict the item from being drawn over the recycler bounds? + // Seems like its possible with enough UI magic val view = viewHolder.itemView @@ -129,7 +127,7 @@ class QueueDragCallback( } override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { - // When an elevated item is done, we reset the elevation using another animation + // When an elevated item is cleared, 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 @@ -148,6 +146,18 @@ class QueueDragCallback( super.clearView(recyclerView, viewHolder) } + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return playbackModel.moveQueueDataItems( + viewHolder.bindingAdapterPosition, + target.bindingAdapterPosition, + queueAdapter + ) + } + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition, queueAdapter) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index 09c9caf36..bbcb5fc20 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -24,7 +24,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowInsets -import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -53,16 +52,11 @@ class QueueFragment : Fragment() { ): View { val binding = FragmentQueueBinding.inflate(inflater) - val callback = QueueDragCallback(playbackModel) { dY -> - // By default, CoordinatorLayout is not aware of scroll events originating from - // when an item is scrolled off-screen, so we manually add a scroll event ourselves. - (binding.queueAppbar.layoutParams as CoordinatorLayout.LayoutParams).behavior - ?.onNestedPreScroll( - binding.queueCoordinator, binding.queueAppbar, - binding.queueRecycler, 0, dY, - IntArray(2), 0 - ) - } + val callback = QueueDragCallback( + playbackModel, + binding.queueCoordinator, + binding.queueAppbar + ) val helper = ItemTouchHelper(callback) val queueAdapter = QueueAdapter(helper, playbackModel) @@ -110,7 +104,6 @@ class QueueFragment : Fragment() { if (isShuffling != lastShuffle) { lastShuffle = isShuffling - binding.queueRecycler.scrollBy(0, 100) binding.queueRecycler.scrollToPosition(0) } } @@ -120,8 +113,8 @@ class QueueFragment : Fragment() { private fun setupEdgeForQueue(binding: FragmentQueueBinding) { if (isEdgeOn()) { - // Account for the side navigation bar if required. binding.root.setOnApplyWindowInsetsListener { v, insets -> + // Account for the side navigation bar if required. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val bars = insets.getInsets(WindowInsets.Type.systemBars()) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt index 2eee8c381..8d53501d6 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/AboutFragment.kt @@ -21,6 +21,7 @@ package org.oxycblt.auxio.settings import android.content.ActivityNotFoundException import android.content.Intent import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -74,27 +75,43 @@ class AboutFragment : Fragment() { Intent.FLAG_ACTIVITY_NEW_TASK ) - val pkgName = requireContext().packageManager.resolveActivity( - browserIntent, PackageManager.MATCH_DEFAULT_ONLY - )?.activityInfo?.packageName - - if (pkgName != null) { - if (pkgName == "android") { - // No default browser [Must open app chooser, may not be supported] - openAppChooser(browserIntent) - } else { - try { - browserIntent.setPackage(pkgName) - startActivity(browserIntent) - } catch (exception: ActivityNotFoundException) { - // Not browser but an app chooser due to OEM garbage - browserIntent.setPackage(null) - openAppChooser(browserIntent) - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // Android 11 seems to now handle the app chooser situations on its own now + // [along with adding a new permission that breaks the old manual code], so + // we just do a typical activity launch. + try { + requireContext().startActivity(browserIntent) + } catch (e: ActivityNotFoundException) { + // No app installed to open the link + requireContext().showToast(R.string.err_no_app) } } else { - // No app installed to open the link - requireContext().showToast(R.string.err_no_app) + // On older versions of android, opening links from an ACTION_VIEW intent might + // not work in all cases, especially when no default app was set. If that is the + // case, we will try to manually handle these cases before we try to launch the + // browser. + val pkgName = requireContext().packageManager.resolveActivity( + browserIntent, PackageManager.MATCH_DEFAULT_ONLY + )?.activityInfo?.packageName + + if (pkgName != null) { + if (pkgName == "android") { + // No default browser [Must open app chooser, may not be supported] + openAppChooser(browserIntent) + } else { + try { + browserIntent.setPackage(pkgName) + startActivity(browserIntent) + } catch (e: ActivityNotFoundException) { + // Not browser but an app chooser due to OEM garbage + browserIntent.setPackage(null) + openAppChooser(browserIntent) + } + } + } else { + // No app installed to open the link + requireContext().showToast(R.string.err_no_app) + } } } diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index bad2095f7..07291e9fa 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -13,5 +13,4 @@ Auxio is a local music player with a fast, reliable UI/UX without the many usele - Audio Focus / Headset Management - No internet connectivity whatsoever - Kotlin from the ground-up -- Modular, feature-based architecture - No rounded corners