home: add fast scroller

Add a new fast-scroller to the home view. This required some annoying
hacks to work, but it seems to work pretty well.
This commit is contained in:
OxygenCobalt 2021-10-13 19:49:20 -06:00
parent 7ef10fa4f8
commit a253cfccc4
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
11 changed files with 131 additions and 26 deletions

View file

@ -86,7 +86,7 @@ dependencies {
// Media // Media
// TODO: Migrate to media2 when I can figure out how to use it // TODO: Migrate to media2 when I can figure out how to use it
implementation "androidx.media:media:1.4.2" implementation "androidx.media:media:1.4.3"
// Preferences // Preferences
implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.preference:preference-ktx:1.1.1"
@ -102,6 +102,8 @@ dependencies {
// Material // Material
implementation "com.google.android.material:material:1.5.0-alpha04" implementation "com.google.android.material:material:1.5.0-alpha04"
implementation 'me.zhanghai.android.fastscroll:library:1.1.7'
// --- DEBUG --- // --- DEBUG ---
// Lint // Lint

View file

@ -29,7 +29,6 @@ import org.oxycblt.auxio.databinding.DialogAccentBinding
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.LifecycleDialog import org.oxycblt.auxio.ui.LifecycleDialog
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.resolveColor
/** /**
* Dialog responsible for showing the list of accents to select. * Dialog responsible for showing the list of accents to select.
@ -55,13 +54,9 @@ class AccentDialog : LifecycleDialog() {
binding.accentRecycler.apply { binding.accentRecycler.apply {
adapter = AccentAdapter(pendingAccent) { accent -> adapter = AccentAdapter(pendingAccent) { accent ->
pendingAccent = accent pendingAccent = accent
updateAccent()
} }
} }
updateAccent()
logD("Dialog created.") logD("Dialog created.")
return binding.root return binding.root
@ -90,15 +85,6 @@ class AccentDialog : LifecycleDialog() {
builder.setNegativeButton(android.R.string.cancel, null) builder.setNegativeButton(android.R.string.cancel, null)
} }
private fun updateAccent() {
val accentColor = pendingAccent.color.resolveColor(requireContext())
(requireDialog() as AlertDialog).apply {
getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(accentColor)
getButton(AlertDialog.BUTTON_NEGATIVE)?.setTextColor(accentColor)
}
}
companion object { companion object {
const val TAG = BuildConfig.APPLICATION_ID + ".tag.ACCENT_PICKER" const val TAG = BuildConfig.APPLICATION_ID + ".tag.ACCENT_PICKER"
const val KEY_PENDING_ACCENT = BuildConfig.APPLICATION_ID + ".key.PENDING_ACCENT" const val KEY_PENDING_ACCENT = BuildConfig.APPLICATION_ID + ".key.PENDING_ACCENT"

View file

@ -23,14 +23,17 @@ import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.iterator import androidx.core.view.iterator
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.core.view.updatePaddingRelative
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import org.oxycblt.auxio.MainFragmentDirections import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
@ -46,10 +49,10 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.SortMode import org.oxycblt.auxio.ui.SortMode
import org.oxycblt.auxio.ui.memberBinding
import org.oxycblt.auxio.util.applyEdge import org.oxycblt.auxio.util.applyEdge
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.util.makeScrollingViewFade
/** /**
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail * The main "Launching Point" fragment of Auxio, allowing navigation to the detail
@ -63,6 +66,7 @@ import org.oxycblt.auxio.util.makeScrollingViewFade
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class HomeFragment : Fragment() { class HomeFragment : Fragment() {
private val binding: FragmentHomeBinding by memberBinding(FragmentHomeBinding::inflate)
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by activityViewModels()
@ -71,7 +75,6 @@ class HomeFragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
val binding = FragmentHomeBinding.inflate(inflater)
val sortItem: MenuItem val sortItem: MenuItem
// --- UI SETUP --- // --- UI SETUP ---
@ -82,7 +85,34 @@ class HomeFragment : Fragment() {
binding.homeAppbar.updatePadding(top = bars.top) binding.homeAppbar.updatePadding(top = bars.top)
} }
binding.homeAppbar.makeScrollingViewFade(binding.homeToolbar) binding.homeAppbar.apply {
addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
binding.homeToolbar.alpha = (binding.homeToolbar.height + verticalOffset) /
binding.homeToolbar.height.toFloat()
}
)
post {
// To add our fast scroller, we need to
val vOffset = (
(layoutParams as CoordinatorLayout.LayoutParams)
.behavior as AppBarLayout.Behavior
).topAndBottomOffset
binding.homePager.updatePaddingRelative(
bottom = binding.homeAppbar.totalScrollRange + vOffset
)
binding.homeAppbar.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
binding.homePager.updatePaddingRelative(
bottom = binding.homeAppbar.totalScrollRange + verticalOffset
)
}
)
}
}
binding.homeToolbar.apply { binding.homeToolbar.apply {
setOnMenuItemClickListener { item -> setOnMenuItemClickListener { item ->

View file

@ -23,11 +23,15 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import me.zhanghai.android.fastscroll.PopupTextProvider
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.HomeFragmentDirections import org.oxycblt.auxio.home.HomeFragmentDirections
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.ui.AlbumViewHolder import org.oxycblt.auxio.ui.AlbumViewHolder
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.SortMode
import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.sliceArticle
class AlbumListFragment : HomeListFragment() { class AlbumListFragment : HomeListFragment() {
override fun onCreateView( override fun onCreateView(
@ -51,6 +55,23 @@ class AlbumListFragment : HomeListFragment() {
return binding.root return binding.root
} }
override val popupProvider: PopupTextProvider
get() = PopupTextProvider { idx ->
val album = homeModel.albums.value!![idx]
when (homeModel.getSortForDisplay(DisplayMode.SHOW_ALBUMS)) {
SortMode.ASCENDING, SortMode.DESCENDING -> album.name.sliceArticle()
.first().uppercase()
SortMode.ARTIST -> album.artist.name.sliceArticle()
.first().uppercase()
SortMode.YEAR -> album.year.toString()
else -> ""
}
}
class AlbumAdapter( class AlbumAdapter(
private val doOnClick: (data: Album) -> Unit, private val doOnClick: (data: Album) -> Unit,
private val doOnLongClick: (view: View, data: Album) -> Unit, private val doOnLongClick: (view: View, data: Album) -> Unit,

View file

@ -23,11 +23,13 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import me.zhanghai.android.fastscroll.PopupTextProvider
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.HomeFragmentDirections import org.oxycblt.auxio.home.HomeFragmentDirections
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.ui.ArtistViewHolder import org.oxycblt.auxio.ui.ArtistViewHolder
import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.sliceArticle
class ArtistListFragment : HomeListFragment() { class ArtistListFragment : HomeListFragment() {
override fun onCreateView( override fun onCreateView(
@ -51,6 +53,11 @@ class ArtistListFragment : HomeListFragment() {
return binding.root return binding.root
} }
override val popupProvider: PopupTextProvider
get() = PopupTextProvider { idx ->
homeModel.artists.value!![idx].name.sliceArticle().first().uppercase()
}
class ArtistAdapter( class ArtistAdapter(
private val doOnClick: (data: Artist) -> Unit, private val doOnClick: (data: Artist) -> Unit,
private val doOnLongClick: (view: View, data: Artist) -> Unit, private val doOnLongClick: (view: View, data: Artist) -> Unit,

View file

@ -23,11 +23,13 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import me.zhanghai.android.fastscroll.PopupTextProvider
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.HomeFragmentDirections import org.oxycblt.auxio.home.HomeFragmentDirections
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.ui.GenreViewHolder import org.oxycblt.auxio.ui.GenreViewHolder
import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.sliceArticle
class GenreListFragment : HomeListFragment() { class GenreListFragment : HomeListFragment() {
override fun onCreateView( override fun onCreateView(
@ -51,6 +53,11 @@ class GenreListFragment : HomeListFragment() {
return binding.root return binding.root
} }
override val popupProvider: PopupTextProvider
get() = PopupTextProvider { idx ->
homeModel.genres.value!![idx].name.sliceArticle().first().uppercase()
}
class GenreAdapter( class GenreAdapter(
private val doOnClick: (data: Genre) -> Unit, private val doOnClick: (data: Genre) -> Unit,
private val doOnLongClick: (view: View, data: Genre) -> Unit, private val doOnLongClick: (view: View, data: Genre) -> Unit,

View file

@ -19,19 +19,25 @@
package org.oxycblt.auxio.home.list package org.oxycblt.auxio.home.list
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import me.zhanghai.android.fastscroll.FastScrollerBuilder
import me.zhanghai.android.fastscroll.PopupTextProvider
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.memberBinding import org.oxycblt.auxio.ui.memberBinding
import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.resolveDrawable
open class HomeListFragment : Fragment() { abstract class HomeListFragment : Fragment() {
protected val binding: FragmentHomeListBinding by memberBinding( protected val binding: FragmentHomeListBinding by memberBinding(
FragmentHomeListBinding::inflate FragmentHomeListBinding::inflate
) )
@ -39,6 +45,20 @@ open class HomeListFragment : Fragment() {
protected val homeModel: HomeViewModel by activityViewModels() protected val homeModel: HomeViewModel by activityViewModels()
protected val playbackModel: PlaybackViewModel by activityViewModels() protected val playbackModel: PlaybackViewModel by activityViewModels()
abstract val popupProvider: PopupTextProvider
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.homeRecycler.apply {
FastScrollerBuilder(this)
.useMd2Style()
.setPopupTextProvider(popupProvider)
.setTrackDrawable(R.drawable.ui_scroll_track.resolveDrawable(context))
.build()
}
}
protected fun <T : BaseModel, VH : RecyclerView.ViewHolder> setupRecycler( protected fun <T : BaseModel, VH : RecyclerView.ViewHolder> setupRecycler(
@IdRes uniqueId: Int, @IdRes uniqueId: Int,
homeAdapter: HomeAdapter<T, VH>, homeAdapter: HomeAdapter<T, VH>,

View file

@ -23,11 +23,16 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import me.zhanghai.android.fastscroll.PopupTextProvider
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemPlayShuffleBinding import org.oxycblt.auxio.databinding.ItemPlayShuffleBinding
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.SongViewHolder import org.oxycblt.auxio.ui.SongViewHolder
import org.oxycblt.auxio.ui.SortMode
import org.oxycblt.auxio.ui.newMenu import org.oxycblt.auxio.ui.newMenu
import org.oxycblt.auxio.ui.sliceArticle
import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater
class SongListFragment : HomeListFragment() { class SongListFragment : HomeListFragment() {
@ -46,10 +51,33 @@ class SongListFragment : HomeListFragment() {
) )
setupRecycler(R.id.home_song_list, adapter, homeModel.songs) setupRecycler(R.id.home_song_list, adapter, homeModel.songs)
binding.homeRecycler.applySpans { it == 0 }
return binding.root return binding.root
} }
override val popupProvider: PopupTextProvider
get() = PopupTextProvider { idx ->
if (idx == 0) {
return@PopupTextProvider ""
}
val song = homeModel.songs.value!![idx]
when (homeModel.getSortForDisplay(DisplayMode.SHOW_SONGS)) {
SortMode.ASCENDING, SortMode.DESCENDING -> song.name.sliceArticle()
.first().uppercase()
SortMode.ARTIST -> song.album.artist.name.sliceArticle()
.first().uppercase()
SortMode.ALBUM -> song.album.name.sliceArticle()
.first().uppercase()
SortMode.YEAR -> song.album.year.toString()
}
}
inner class SongsAdapter( inner class SongsAdapter(
private val doOnClick: (data: Song) -> Unit, private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (view: View, data: Song) -> Unit, private val doOnLongClick: (view: View, data: Song) -> Unit,

View file

@ -46,7 +46,6 @@ import org.oxycblt.auxio.util.applyEdge
import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.applySpans
import org.oxycblt.auxio.util.getSystemServiceSafe import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.makeScrollingViewFade
/** /**
* A [Fragment] that allows for the searching of the entire music library. * A [Fragment] that allows for the searching of the entire music library.
@ -81,8 +80,6 @@ class SearchFragment : Fragment() {
binding.searchAppbar.updatePadding(top = bars.top) binding.searchAppbar.updatePadding(top = bars.top)
} }
binding.searchAppbar.makeScrollingViewFade(binding.searchToolbar)
binding.searchToolbar.apply { binding.searchToolbar.apply {
val itemId = when (searchModel.filterMode) { val itemId = when (searchModel.filterMode) {
DisplayMode.SHOW_SONGS -> R.id.option_filter_songs DisplayMode.SHOW_SONGS -> R.id.option_filter_songs

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid
android:color="@android:color/transparent" />
<size android:width="@dimen/spacing_small" />
</shape>

View file

@ -6,6 +6,7 @@
<style name="Theme.Auxio.V27" parent="Theme.Auxio" /> <style name="Theme.Auxio.V27" parent="Theme.Auxio" />
<!-- Android 12 configuration --> <!-- Android 12 configuration -->
<style name="Theme.Auxio.V31" parent="Theme.Auxio.V27"> <style name="Theme.Auxio.V31" parent="Theme.Auxio.V27">
<!-- Make sure to apply more accent-friendly values on older versions -->
<item name="colorSecondary">?attr/colorPrimary</item> <item name="colorSecondary">?attr/colorPrimary</item>
<item name="colorOnSecondary">?attr/colorOnPrimary</item> <item name="colorOnSecondary">?attr/colorOnPrimary</item>
<item name="colorSecondaryContainer">?attr/colorPrimaryContainer</item> <item name="colorSecondaryContainer">?attr/colorPrimaryContainer</item>
@ -21,9 +22,6 @@
<item name="colorControlNormal">@color/control</item> <item name="colorControlNormal">@color/control</item>
<item name="colorControlHighlight">@color/overlay_selection</item> <item name="colorControlHighlight">@color/overlay_selection</item>
<item name="colorControlActivated">?attr/colorPrimary</item> <item name="colorControlActivated">?attr/colorPrimary</item>
<item name="android:textColorHighlight">@color/overlay_text_highlight</item>
<item name="android:textColorHighlightInverse">@color/overlay_text_highlight_inverse</item>
</style> </style>
<!-- Base theme --> <!-- Base theme -->
@ -31,7 +29,9 @@
<!-- Values --> <!-- Values -->
<item name="colorOutline">@color/overlay_stroke</item> <item name="colorOutline">@color/overlay_stroke</item>
<!-- Appearance --> <!-- Android component magic -->
<item name="android:textColorHighlight">@color/overlay_text_highlight</item>
<item name="android:textColorHighlightInverse">@color/overlay_text_highlight_inverse</item>
<item name="android:colorBackground">?attr/colorSurface</item> <item name="android:colorBackground">?attr/colorSurface</item>
<item name="android:windowBackground">?attr/colorSurface</item> <item name="android:windowBackground">?attr/colorSurface</item>
<item name="android:fontFamily">@font/inter</item> <item name="android:fontFamily">@font/inter</item>