From c5fcc45ee971e3c1a935136046fbb5e4c22e28eb Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Mon, 8 Nov 2021 19:54:38 -0700 Subject: [PATCH] detail: show name when scrolling Show the name of the currently shown detail item when scrolling. This is just UI candy that I've always wanted to add but couldn't due to CollapsingToolbarLayout being a mess. This addition circumvents that by simply doing some reflection magic and hooking the alpha of the toolbar title to the current scroll state, solving the issue. --- .../auxio/detail/AlbumDetailFragment.kt | 2 +- .../auxio/detail/ArtistDetailFragment.kt | 2 +- .../auxio/detail/DetailAppBarLayout.kt | 133 ++++++++++++++++++ .../oxycblt/auxio/detail/DetailFragment.kt | 5 + .../auxio/detail/GenreDetailFragment.kt | 2 +- .../fastscroll/FastScrollPopupDrawable.kt | 1 + .../auxio/playback/PlaybackBarLayout.kt | 1 + .../org/oxycblt/auxio/ui/EdgeAppBarLayout.kt | 2 +- app/src/main/res/layout/fragment_detail.xml | 4 +- 9 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 0ae057081..e03ac50ff 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -64,7 +64,7 @@ class AlbumDetailFragment : DetailFragment() { binding.lifecycleOwner = viewLifecycleOwner - setupToolbar(R.menu.menu_album_detail) { itemId -> + setupToolbar(detailModel.curAlbum.value!!, R.menu.menu_album_detail) { itemId -> if (itemId == R.id.action_queue_add) { playbackModel.addToUserQueue(detailModel.curAlbum.value!!) requireContext().showToast(R.string.lbl_queue_added) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index 6a3794798..775fd38a1 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -73,7 +73,7 @@ class ArtistDetailFragment : DetailFragment() { binding.lifecycleOwner = viewLifecycleOwner - setupToolbar() + setupToolbar(detailModel.curArtist.value!!) setupRecycler(detailAdapter) { pos -> // If the item is an ActionHeader we need to also make the item full-width val item = detailAdapter.currentList[pos] diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt new file mode 100644 index 000000000..267a96735 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailAppBarLayout.kt @@ -0,0 +1,133 @@ +package org.oxycblt.auxio.detail + +import android.animation.ValueAnimator +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import androidx.annotation.StyleRes +import androidx.appcompat.widget.AppCompatTextView +import androidx.appcompat.widget.Toolbar +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.appbar.AppBarLayout +import org.oxycblt.auxio.R +import org.oxycblt.auxio.ui.EdgeAppBarLayout + +/** + * An [EdgeAppBarLayout] variant that also shows the name of the toolbar whenever the detail + * recyclerview is scrolled beyond it's first item (a.k.a the header). This is used instead of + * CollapsingToolbarLayout since that thing is a mess with crippling bugs and state issues. + * This just works. + */ +class DetailAppBarLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + @StyleRes defStyleAttr: Int = -1 +) : EdgeAppBarLayout(context, attrs, defStyleAttr) { + private var mTitleView: AppCompatTextView? = null + private var mRecycler: RecyclerView? = null + + private var titleShown: Boolean? = null + private var mTitleAnimator: ValueAnimator? = null + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + (layoutParams as CoordinatorLayout.LayoutParams).behavior = Behavior(context) + } + + private fun findTitleView(): AppCompatTextView { + val titleView = mTitleView + + if (titleView != null) { + return titleView + } + + val toolbar = findViewById(R.id.detail_toolbar) + + val newTitleView = Toolbar::class.java.getDeclaredField("mTitleTextView").run { + isAccessible = true + get(toolbar) as AppCompatTextView + } + + newTitleView.alpha = 0f + + mTitleView = newTitleView + return newTitleView + } + + private fun findRecyclerView(): RecyclerView { + val recycler = mRecycler + + if (recycler != null) { + return recycler + } + + val newRecycler = (parent as ViewGroup).findViewById(R.id.detail_recycler) + + mRecycler = newRecycler + return newRecycler + } + + private fun setTitleVisibility(visible: Boolean) { + if (titleShown == visible) return + + titleShown = visible + + if (mTitleAnimator != null) { + mTitleAnimator!!.cancel() + mTitleAnimator = null + } + + val titleView = findTitleView() + val from: Float + val to: Float + + if (visible) { + from = 0f + to = 1f + } else { + from = 1f + to = 0f + } + + if (titleView.alpha == to) return + + mTitleAnimator = ValueAnimator.ofFloat(from, to).apply { + addUpdateListener { + titleView.alpha = it.animatedValue as Float + } + + duration = resources.getInteger(R.integer.app_bar_elevation_anim_duration).toLong() + + start() + } + } + + class Behavior @JvmOverloads constructor( + context: Context? = null, + attrs: AttributeSet? = null + ) : AppBarLayout.Behavior(context, attrs) { + override fun onNestedPreScroll( + coordinatorLayout: CoordinatorLayout, + child: AppBarLayout, + target: View, + dx: Int, + dy: Int, + consumed: IntArray, + type: Int + ) { + super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type) + + val appBar = child as DetailAppBarLayout + val recycler = appBar.findRecyclerView() + + val showTitle = (recycler.layoutManager as LinearLayoutManager) + .findFirstVisibleItemPosition() > 0 + + appBar.setTitleVisibility(showTitle) + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt index cd9bd0365..070c95eae 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -31,6 +31,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentDetailBinding +import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.SortMode import org.oxycblt.auxio.ui.memberBinding @@ -70,14 +71,18 @@ abstract class DetailFragment : Fragment() { /** * Shortcut method for doing setup of the detail toolbar. + * @param music Music data to use as the toolbar title * @param menu Menu resource to use * @param onMenuClick (Optional) a click listener for that menu */ protected fun setupToolbar( + data: Music, @MenuRes menu: Int = -1, onMenuClick: ((itemId: Int) -> Boolean)? = null ) { binding.detailToolbar.apply { + title = data.name + if (menu != -1) { inflateMenu(menu) } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index f900e55a3..2a1f471b7 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -64,7 +64,7 @@ class GenreDetailFragment : DetailFragment() { binding.lifecycleOwner = viewLifecycleOwner - setupToolbar() + setupToolbar(detailModel.curGenre.value!!) setupRecycler(detailAdapter) { pos -> val item = detailAdapter.currentList[pos] item is Header || item is ActionHeader || item is Genre diff --git a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt index ba0fb1da6..aca7c3d41 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/fastscroll/FastScrollPopupDrawable.kt @@ -47,6 +47,7 @@ import kotlin.math.sqrt * - Use modified Auxio resources instead of AFS resources * - Variable names are no longer prefixed with m * - Made path management compat-friendly + * - Converted to kotlin */ class FastScrollPopupDrawable(context: Context) : Drawable() { private val paint: Paint = Paint().apply { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarLayout.kt index 68d3c566e..599d721ed 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackBarLayout.kt @@ -38,6 +38,7 @@ import org.oxycblt.auxio.util.systemBarsCompat * this class was primarily written by me. * * TODO: Add a swipe-up behavior a la Phonograph. I think that would improve UX. + * - We need to use a separate drag helper to prevent issues * TODO: Leverage this layout to make more tablet-friendly UIs * * @author OxygenCobalt diff --git a/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt index 41b773a68..5dd3b8f8c 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/EdgeAppBarLayout.kt @@ -38,7 +38,7 @@ import org.oxycblt.auxio.util.systemBarsCompat * **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. */ -class EdgeAppBarLayout @JvmOverloads constructor( +open class EdgeAppBarLayout @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, @StyleRes defStyleAttr: Int = -1 diff --git a/app/src/main/res/layout/fragment_detail.xml b/app/src/main/res/layout/fragment_detail.xml index 43942338c..db841e50b 100644 --- a/app/src/main/res/layout/fragment_detail.xml +++ b/app/src/main/res/layout/fragment_detail.xml @@ -13,7 +13,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - +