From a4d2a8d48c5c555765a279d7505ea324e28e17b3 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 29 Aug 2021 20:20:25 -0600 Subject: [PATCH] search: improve ui Improve the search UI by making it edge-to-edge and adding the liftOnScroll idiom. It does come with the caveat of walking on eggshells to get the liftOnScroll code working, but its okay. It may be improved in the future. --- .../oxycblt/auxio/detail/DetailFragment.kt | 6 ++++ .../org/oxycblt/auxio/home/HomeFragment.kt | 13 ++------- .../auxio/playback/queue/QueueFragment.kt | 2 ++ .../oxycblt/auxio/search/SearchFragment.kt | 17 ++++++++--- .../java/org/oxycblt/auxio/util/ViewUtil.kt | 29 +++++++++++++++++++ app/src/main/res/layout/fragment_search.xml | 2 +- 6 files changed, 53 insertions(+), 16 deletions(-) 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 d97fd43b2..c38031d01 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -22,6 +22,7 @@ import android.os.Bundle import android.view.View import androidx.activity.OnBackPressedCallback import androidx.annotation.MenuRes +import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController @@ -30,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView import org.oxycblt.auxio.databinding.FragmentDetailBinding import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.memberBinding +import org.oxycblt.auxio.util.applyEdge import org.oxycblt.auxio.util.isLandscape /** @@ -42,6 +44,10 @@ abstract class DetailFragment : Fragment() { protected val binding by memberBinding(FragmentDetailBinding::inflate) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding.applyEdge { bars -> + binding.detailAppbar.updatePadding(top = bars.top) + } + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback) } 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 058baf049..ac7db0274 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -29,7 +29,6 @@ import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 -import com.google.android.material.appbar.AppBarLayout import com.google.android.material.tabs.TabLayoutMediator import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.R @@ -44,6 +43,7 @@ import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.util.applyEdge import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE +import org.oxycblt.auxio.util.makeScrollingViewFade /** * The main "Launching Point" fragment of Auxio, allowing navigation to the detail @@ -74,16 +74,7 @@ class HomeFragment : Fragment() { binding.homeAppbar.updatePadding(top = bars.top) } - // There is basically no way to prevent the toolbar to draw under the status bar when - // it collapses, so do the next best thing and fade it out so it doesn't stick out like - // a sore thumb. As a side effect, this looks really cool. - binding.homeAppbar.addOnOffsetChangedListener( - AppBarLayout.OnOffsetChangedListener { _, verticalOffset -> - binding.homeToolbar.apply { - alpha = (height + verticalOffset) / height.toFloat() - } - } - ) + binding.homeAppbar.makeScrollingViewFade(binding.homeToolbar) binding.homeToolbar.setOnMenuItemClickListener { item -> when (item.itemId) { 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 494c4eaac..9276271bd 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 @@ -27,6 +27,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.ItemTouchHelper +import kotlinx.coroutines.NonDisposableHandle.parent import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentQueueBinding import org.oxycblt.auxio.music.BaseModel @@ -105,6 +106,7 @@ class QueueFragment : Fragment() { lastShuffle = isShuffling binding.queueRecycler.scrollToPosition(0) + binding.queueAppbar.isLifted = false // Make sure lifted state changes. } } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index 7b5889d73..15f20f787 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -24,6 +24,7 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import androidx.core.view.postDelayed +import androidx.core.view.updatePadding import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -42,9 +43,11 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.newMenu +import org.oxycblt.auxio.util.applyEdge import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.getSystemServiceSafe import org.oxycblt.auxio.util.logD +import org.oxycblt.auxio.util.makeScrollingViewFade /** * A [Fragment] that allows for the searching of the entire music library. @@ -79,6 +82,12 @@ class SearchFragment : Fragment() { binding.lifecycleOwner = viewLifecycleOwner + binding.applyEdge { bars -> + binding.searchAppbar.updatePadding(top = bars.top) + } + + binding.searchAppbar.makeScrollingViewFade(binding.searchToolbar) + binding.searchToolbar.apply { val itemId = when (searchModel.filterMode) { DisplayMode.SHOW_SONGS -> R.id.option_filter_songs @@ -131,18 +140,18 @@ class SearchFragment : Fragment() { searchModel.searchResults.observe(viewLifecycleOwner) { results -> searchAdapter.submitList(results) { + // We've just scrolled back to the top, reset the lifted state + // TODO: Maybe find a better way to keep scroll state when the search + // results didn't actually change. binding.searchRecycler.scrollToPosition(0) + binding.searchAppbar.isLifted = false } if (results.isEmpty()) { - // If the data is empty, then the ability for the toolbar to collapse - // on scroll should be disabled. binding.searchAppbar.setExpanded(true) binding.searchRecycler.visibility = View.GONE - toolbarParams.scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL } else { binding.searchRecycler.visibility = View.VISIBLE - toolbarParams.scrollFlags = defaultParams } } 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 5999e010f..ea5c6cab0 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ViewUtil.kt @@ -32,11 +32,13 @@ import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.annotation.DrawableRes +import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.view.updatePadding import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding +import com.google.android.material.appbar.AppBarLayout import org.oxycblt.auxio.R /** @@ -140,6 +142,33 @@ fun @receiver:AttrRes Int.resolveAttr(context: Context): Int { return color.resolveColor(context) } +/** + * Make this [AppBarLayout] fade a scrolling [view] out when it collapses. + * This is mostly because I am unable to figure out how to get a collapsing view not + * to draw under the status bar in edge-to-edge mode. + */ +fun AppBarLayout.makeScrollingViewFade(view: View) { + addOnOffsetChangedListener( + AppBarLayout.OnOffsetChangedListener { _, verticalOffset -> + view.alpha = (view.height + verticalOffset) / view.height.toFloat() + } + ) +} + +/** + * Force-update this [AppBarLayout]'s lifted state. This is useful when the dataset changes + * and the lifted state must be updated. + */ +fun AppBarLayout.updateLiftedState(recycler: RecyclerView) { + post { + val coordinator = (parent as CoordinatorLayout) + + (layoutParams as CoordinatorLayout.LayoutParams).behavior?.onNestedPreScroll( + coordinator, this, recycler, 0, 0, IntArray(2), 0 + ) + } +} + /** * Apply edge-to-edge tweaks to the root of a [ViewBinding]. * @param onApply What to do when the system bar insets are provided diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 583068ad3..6cf9affe0 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -12,7 +12,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/colorSurface" - android:elevation="@dimen/elevation_normal"> + app:liftOnScroll="true">