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 ---
// 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
implementation 'io.coil-kt:coil:0.13.0'
// Material
implementation 'com.google.android.material:material:1.3.0-alpha03'
// 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"
// Fast-Scroll [Too lazy to make it myself]
implementation 'com.reddit:indicator-fast-scroll:1.3.0'
// --- DEV ---
// Lint
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
class SongAdapter(
private val data: List<Song>,
val data: List<Song>,
private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (data: Song, view: View) -> Unit
) : RecyclerView.Adapter<SongViewHolder>() {

View file

@ -1,5 +1,6 @@
package org.oxycblt.auxio.songs
import android.content.res.ColorStateList
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@ -8,12 +9,17 @@ import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment
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.databinding.FragmentSongsBinding
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.accent
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
@ -32,8 +38,17 @@ class SongsFragment : Fragment() {
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: Fast scrolling?
// --- UI SETUP ---
@ -45,16 +60,75 @@ class SongsFragment : Fragment() {
}
binding.songRecycler.apply {
adapter = SongAdapter(
musicStore.songs,
doOnClick = { playbackModel.playSong(it, PlaybackMode.ALL_SONGS) },
doOnLongClick = { data, view ->
PopupMenu(requireContext(), view).setupSongActions(
data, requireContext(), playbackModel
adapter = songAdapter
setHasFixedSize(true)
}
binding.songFastScroll.apply {
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.")

View file

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context=".songs.SongsFragment">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
@ -16,17 +16,38 @@
android:layout_height="?android:attr/actionBarSize"
android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
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
android:id="@+id/song_recycler"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
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" />
</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>

View file

@ -47,6 +47,14 @@
<item name="android:popupBackground">@color/background</item>
</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]
https://stackoverflow.com/a/57790787/14143986