Add fast scrolling to SongsFragment
Add a fast-scroll bar to the list of songs in SongsFragment.
This commit is contained in:
parent
38afa8f4d2
commit
3fca28dd20
5 changed files with 127 additions and 19 deletions
|
@ -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"
|
||||||
|
|
|
@ -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>() {
|
||||||
|
|
|
@ -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.")
|
||||||
|
|
|
@ -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>
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue