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 ac7db0274..2e52cfd39 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -116,6 +116,26 @@ class HomeFragment : Fragment() { logE("Unable to reduce ViewPager sensitivity") logE(e.stackTraceToString()) } + + // We know that there will only be a fixed amount of tabs, so we manually set this + // limit to that. This also prevents the appbar lift state from being confused during + // page transitions. + offscreenPageLimit = homeModel.tabs.value!!.size + + // ViewPager2 tends to garble any scrolling view events that occur within it's + // fragments, so we fix that by instructing our AppBarLayout to follow the specific + // view we have just selected. + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + binding.homeAppbar.liftOnScrollTargetViewId = + when (homeModel.tabs.value!![position]) { + DisplayMode.SHOW_SONGS -> R.id.home_song_list + DisplayMode.SHOW_ALBUMS -> R.id.home_album_list + DisplayMode.SHOW_ARTISTS -> R.id.home_artist_list + DisplayMode.SHOW_GENRES -> R.id.home_genre_list + } + } + }) } TabLayoutMediator(binding.homeTabs, binding.homePager) { tab, pos -> @@ -132,7 +152,7 @@ class HomeFragment : Fragment() { // --- VIEWMODEL SETUP --- detailModel.navToItem.observe(viewLifecycleOwner) { item -> - // The AppBarLayout bugs out and collapses when we navigate too fast, wait for it + // The AppBarLayout gets confused and collapses when we navigate too fast, wait for it // to draw before we continue. binding.homeAppbar.post { when (item) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeListFragment.kt index ed43011a0..4218f499a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeListFragment.kt @@ -22,13 +22,17 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.annotation.IdRes import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.LiveData import androidx.navigation.fragment.findNavController import org.oxycblt.auxio.BuildConfig +import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel @@ -77,9 +81,38 @@ class HomeListFragment : Fragment() { ::newMenu ) + // --- ITEM SETUP --- + + // Get some tab-specific values before we go ahead. More specifically, the data to use + // and the unique ID that HomeFragment's AppBarLayout uses to determine lift state. + val pos = requireNotNull(arguments).getInt(ARG_POS) + + @IdRes val customId: Int + val toObserve: LiveData> + + when (requireNotNull(homeModel.tabs.value)[pos]) { + DisplayMode.SHOW_SONGS -> { + customId = R.id.home_song_list + toObserve = homeModel.songs + } + DisplayMode.SHOW_ALBUMS -> { + customId = R.id.home_album_list + toObserve = homeModel.albums + } + DisplayMode.SHOW_ARTISTS -> { + customId = R.id.home_artist_list + toObserve = homeModel.artists + } + DisplayMode.SHOW_GENRES -> { + customId = R.id.home_genre_list + toObserve = homeModel.genres + } + } + // --- UI SETUP --- binding.homeRecycler.apply { + id = customId adapter = homeAdapter setHasFixedSize(true) applySpans() @@ -87,15 +120,6 @@ class HomeListFragment : Fragment() { // --- VIEWMODEL SETUP --- - val pos = requireNotNull(arguments).getInt(ARG_POS) - - val toObserve = when (requireNotNull(homeModel.tabs.value)[pos]) { - DisplayMode.SHOW_SONGS -> homeModel.songs - DisplayMode.SHOW_ALBUMS -> homeModel.albums - DisplayMode.SHOW_ARTISTS -> homeModel.artists - DisplayMode.SHOW_GENRES -> homeModel.genres - } - // Make sure that this RecyclerView has data before startup homeAdapter.updateData(toObserve.value!!) 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 c5a6c9890..6c737a545 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/LiftAppBarLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/LiftAppBarLayout.kt @@ -20,17 +20,22 @@ package org.oxycblt.auxio.ui import android.content.Context import android.util.AttributeSet +import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import androidx.annotation.StyleRes import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.content.res.ResourcesCompat import androidx.core.view.children import androidx.recyclerview.widget.RecyclerView import com.google.android.material.appbar.AppBarLayout +import org.oxycblt.auxio.util.logE /** * An [AppBarLayout] that fixes a bug with the default implementation where the lifted state * 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 */ @@ -39,15 +44,17 @@ class LiftAppBarLayout @JvmOverloads constructor( attrs: AttributeSet? = null, @StyleRes defStyleAttr: Int = -1 ) : AppBarLayout(context, attrs, defStyleAttr) { - private var recycler: RecyclerView? = null + private var scrollingChild: View? = null private val tConsumed = IntArray(2) private val onPreDraw = ViewTreeObserver.OnPreDrawListener { - recycler?.let { rec -> - val coordinator = (parent as CoordinatorLayout) + val child = findScrollingChild() + + if (child != null) { + val coordinator = parent as CoordinatorLayout (layoutParams as CoordinatorLayout.LayoutParams).behavior?.onNestedPreScroll( - coordinator, this, rec, 0, 0, tConsumed, 0 + coordinator, this, coordinator, 0, 0, tConsumed, 0 ) } @@ -58,17 +65,31 @@ class LiftAppBarLayout @JvmOverloads constructor( viewTreeObserver.addOnPreDrawListener(onPreDraw) } - override fun onAttachedToWindow() { - super.onAttachedToWindow() - - // Assume there is one RecyclerView [Because there is] - recycler = (parent as ViewGroup).children.firstOrNull { it is RecyclerView } - as RecyclerView? - } - override fun onDetachedFromWindow() { super.onDetachedFromWindow() viewTreeObserver.removeOnPreDrawListener(onPreDraw) } + + override fun setLiftOnScrollTargetViewId(liftOnScrollTargetViewId: Int) { + super.setLiftOnScrollTargetViewId(liftOnScrollTargetViewId) + + // Sometimes we dynamically set the scrolling child [such as in HomeFragment], so clear it + // and re-draw when that occurs. + scrollingChild = null + onPreDraw.onPreDraw() + } + + private fun findScrollingChild(): View? { + // Roll some custom code for finding our scrolling view. This can be anything as long as + // it updates this layout in it's onNestedPreScroll call. + if (scrollingChild == null) { + if (liftOnScrollTargetViewId != ResourcesCompat.ID_NULL) { + scrollingChild = (parent as ViewGroup).findViewById(liftOnScrollTargetViewId) + } else { + logE("liftOnScrollTargetViewId was not specified. ignoring scroll events.") + } + } + return scrollingChild + } } diff --git a/app/src/main/res/layout/fragment_detail.xml b/app/src/main/res/layout/fragment_detail.xml index 684e89073..0b109667b 100644 --- a/app/src/main/res/layout/fragment_detail.xml +++ b/app/src/main/res/layout/fragment_detail.xml @@ -13,7 +13,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/colorSurface" - app:liftOnScroll="true"> + app:liftOnScroll="true" + app:liftOnScrollTargetViewId="@id/detail_recycler"> - + android:focusable="true" + app:liftOnScroll="true"> - + + app:liftOnScroll="true" + app:liftOnScrollTargetViewId="@id/queue_recycler"> + app:liftOnScroll="true" + app:liftOnScrollTargetViewId="@id/search_recycler"> + + + + + + + \ No newline at end of file