Add fast scrolling to SongsFragment

Add a fast-scroll bar to the list of songs in SongsFragment.
This commit is contained in:
OxygenCobalt 2020-11-25 16:27:25 -07:00
parent 38afa8f4d2
commit 3fca28dd20
5 changed files with 127 additions and 19 deletions

View file

@ -77,16 +77,21 @@ dependencies {
// --- THIRD PARTY --- // --- THIRD PARTY ---
// ExoPlayer
def exoplayer_version = "2.12.1"
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:extension-mediasession:$exoplayer_version"
// Image loading // Image loading
implementation 'io.coil-kt:coil:0.13.0' implementation 'io.coil-kt:coil:0.13.0'
// Material // Material
implementation 'com.google.android.material:material:1.3.0-alpha03' implementation 'com.google.android.material:material:1.3.0-alpha03'
// ExoPlayer // Fast-Scroll [Too lazy to make it myself]
def exoplayer_version = "2.12.1" implementation 'com.reddit:indicator-fast-scroll:1.3.0'
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:extension-mediasession:$exoplayer_version" // --- DEV ---
// Lint // Lint
ktlint "com.pinterest:ktlint:0.37.2" ktlint "com.pinterest:ktlint:0.37.2"

View file

@ -7,7 +7,7 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.viewholders.SongViewHolder import org.oxycblt.auxio.recycler.viewholders.SongViewHolder
class SongAdapter( class SongAdapter(
private val data: List<Song>, val data: List<Song>,
private val doOnClick: (data: Song) -> Unit, private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (data: Song, view: View) -> Unit private val doOnLongClick: (data: Song, view: View) -> Unit
) : RecyclerView.Adapter<SongViewHolder>() { ) : RecyclerView.Adapter<SongViewHolder>() {

View file

@ -1,5 +1,6 @@
package org.oxycblt.auxio.songs package org.oxycblt.auxio.songs
import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -8,12 +9,17 @@ import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.reddit.indicatorfastscroll.FastScrollerView
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentSongsBinding import org.oxycblt.auxio.databinding.FragmentSongsBinding
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.setupSongActions import org.oxycblt.auxio.ui.setupSongActions
import org.oxycblt.auxio.ui.toColor
/** /**
* A [Fragment] that shows a list of all songs on the device. Contains options to search/shuffle * A [Fragment] that shows a list of all songs on the device. Contains options to search/shuffle
@ -32,8 +38,17 @@ class SongsFragment : Fragment() {
val musicStore = MusicStore.getInstance() val musicStore = MusicStore.getInstance()
val songAdapter = SongAdapter(
musicStore.songs,
doOnClick = { playbackModel.playSong(it, PlaybackMode.ALL_SONGS) },
doOnLongClick = { data, view ->
PopupMenu(requireContext(), view).setupSongActions(
data, requireContext(), playbackModel
)
}
)
// TODO: Add option to search songs [Or just make a dedicated tab] // TODO: Add option to search songs [Or just make a dedicated tab]
// TODO: Fast scrolling?
// --- UI SETUP --- // --- UI SETUP ---
@ -45,16 +60,75 @@ class SongsFragment : Fragment() {
} }
binding.songRecycler.apply { binding.songRecycler.apply {
adapter = SongAdapter( adapter = songAdapter
musicStore.songs, setHasFixedSize(true)
doOnClick = { playbackModel.playSong(it, PlaybackMode.ALL_SONGS) }, }
doOnLongClick = { data, view ->
PopupMenu(requireContext(), view).setupSongActions( binding.songFastScroll.apply {
data, requireContext(), playbackModel var hasAddedNumber = false
var iters = 0
setupWithRecyclerView(
binding.songRecycler,
{ pos ->
val item = musicStore.songs[pos]
iters++
var char = item.name[0].toUpperCase()
// If the item starts with "the"/"a", then actually use the character after that
// as its initial. Yes, this is stupidly anglo-centric but the code [hopefully]
// shouldn't run with other languages.
if (item.name.length > 5 &&
item.name.startsWith("the ", ignoreCase = true)
) {
char = item.name[4].toUpperCase()
} else if (item.name.length > 3 &&
item.name.startsWith("a ", ignoreCase = true)
) {
char = item.name[2].toUpperCase()
}
// Check if this song starts with a number, if so, then concat it with a single
// "Numeric" item if haven't already.
// This check only occurs on the second time the fast scroller is polled for items.
if (iters >= musicStore.songs.size) {
if (char.isDigit()) {
if (!hasAddedNumber) {
hasAddedNumber = true
} else {
return@setupWithRecyclerView null
}
}
}
FastScrollItemIndicator.Text(
char.toString()
) )
} }
) )
setHasFixedSize(true)
textAppearanceRes = R.style.TextAppearance_FastScroll
textColor = ColorStateList.valueOf(accent.first.toColor(requireContext()))
useDefaultScroller = false
itemIndicatorSelectedCallbacks.add(
object : FastScrollerView.ItemIndicatorSelectedCallback {
override fun onItemIndicatorSelected(
indicator: FastScrollItemIndicator,
indicatorCenterY: Int,
itemPosition: Int
) {
val layoutManager = binding.songRecycler.layoutManager
as LinearLayoutManager
layoutManager.scrollToPositionWithOffset(itemPosition, 0)
}
}
)
binding.songFastScrollThumb.setupWithFastScroller(this)
binding.songFastScrollThumb.textAppearanceRes = R.style.TextAppearance_ThumbIndicator
} }
Log.d(this::class.simpleName, "Fragment created.") Log.d(this::class.simpleName, "Fragment created.")

View file

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context=".songs.SongsFragment"> tools:context=".songs.SongsFragment">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
@ -16,17 +16,38 @@
android:layout_height="?android:attr/actionBarSize" android:layout_height="?android:attr/actionBarSize"
android:background="?android:attr/windowBackground" android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal" android:elevation="@dimen/elevation_normal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/menu_songs" app:menu="@menu/menu_songs"
app:titleTextAppearance="@style/TextAppearance.Toolbar.Header" app:title="@string/label_all_songs"
app:title="@string/label_all_songs" /> app:titleTextAppearance="@style/TextAppearance.Toolbar.Header" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/song_recycler" android:id="@+id/song_recycler"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/song_fast_scroll"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/song_toolbar"
tools:listitem="@layout/item_song" /> tools:listitem="@layout/item_song" />
</LinearLayout> <com.reddit.indicatorfastscroll.FastScrollerView
android:id="@+id/song_fast_scroll"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/song_toolbar" />
<com.reddit.indicatorfastscroll.FastScrollerThumbView
android:id="@+id/song_fast_scroll_thumb"
android:layout_width="50dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/song_fast_scroll"
app:layout_constraintTop_toBottomOf="@+id/song_toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -47,6 +47,14 @@
<item name="android:popupBackground">@color/background</item> <item name="android:popupBackground">@color/background</item>
</style> </style>
<style name="TextAppearance.FastScroll" parent="TextAppearance.AppCompat.Body2">
<item name="android:fontFamily">@font/inter_semibold</item>
</style>
<style name="TextAppearance.ThumbIndicator" parent="TextAppearance.FastScroll">
<item name="android:textSize">18sp</item>
</style>
<!-- <!--
Fix to get QueueFragment to not overlap the Status Bar or Navigation Bar [Currently unused but still here] Fix to get QueueFragment to not overlap the Status Bar or Navigation Bar [Currently unused but still here]
https://stackoverflow.com/a/57790787/14143986 https://stackoverflow.com/a/57790787/14143986