library: tap-to-top and shuffle fab for long song lists

This commit is contained in:
austinried
2026-10-09 09:15:36 +00:00
parent 15d77f998f
commit 0bdac8fa22
15 changed files with 234 additions and 4 deletions
@@ -4,6 +4,7 @@ import android.content.Context
import android.content.res.Resources
import androidx.media3.common.PlaybackException
import androidx.room3.Room
import androidx.room3.useWriterConnection
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -2391,6 +2392,27 @@ class PlaybackControllerTest {
assertEquals(setOf("a1", "a2", "a3", "b1", "b2"), handle.items.map { it.id }.toSet())
}
@Test
fun shuffleGenrePlaysOnlyThatGenresSongs() {
seedGenre()
controller.shuffleGenre(1, "Rock")
await { controller.state.value.item != null && controller.state.value.shuffle }
assertTrue(controller.state.value.shuffle)
assertEquals(
QueueKind.Genre,
controller.state.value.context
?.kind,
)
assertEquals(
"Rock",
controller.state.value.context
?.refId,
)
assertEquals(setOf("r1", "r2"), handle.items.map { it.id }.toSet())
}
@Test
fun artistQueueRowsFollowTheFlatShuffleIdOrder() {
seedArtist()
@@ -3100,6 +3122,26 @@ class PlaybackControllerTest {
}
}
private fun seedGenre() {
runBlocking {
db.sourcesDao().upsertSource(Source(1, "source 1", "http://localhost:1", true, 1))
db.libraryDao().upsertSongs(
listOf(
Song(1, "r1", "al1", "ar1", "Rock 1", "Album", "Artist", 100, 1, 1, null, null),
Song(1, "r2", "al1", "ar1", "Rock 2", "Album", "Artist", 100, 2, 1, null, null),
Song(1, "j1", "al1", "ar1", "Jazz 1", "Album", "Artist", 100, 3, 1, null, null),
),
)
db.useWriterConnection { connection ->
listOf("r1" to "Rock", "r2" to "Rock", "j1" to "Jazz").forEach { (songId, genre) ->
connection.usePrepared(
"INSERT INTO song_genres (sourceId, songId, position, genre) VALUES (1, '$songId', 0, '$genre')",
) { it.step() }
}
}
}
}
private fun seedAlbumTracks(
albumId: String,
count: Int,
@@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
@@ -55,4 +56,33 @@ class ViewportFillTest {
assertEquals(initial, fillHeight)
}
@Test
fun scrollToTopOnRequestMovesOnlyOnANewRequest() {
lateinit var state: LazyListState
val request = mutableStateOf<Any?>(null)
composeRule.setContent {
SubtracksTheme {
state = rememberLazyListState()
ScrollToTopOnRequest(request.value, state)
LazyColumn(state = state, modifier = Modifier.fillMaxSize()) {
items(50) { Text("item $it", modifier = Modifier.height(56.dp)) }
}
}
}
composeRule.waitForIdle()
composeRule.runOnIdle { runBlocking { state.scrollToItem(10) } }
composeRule.waitForIdle()
assertEquals(10, state.firstVisibleItemIndex)
composeRule.runOnIdle { request.value = 0 }
composeRule.waitForIdle()
assertEquals("selecting a page must keep its scroll position", 10, state.firstVisibleItemIndex)
composeRule.runOnIdle { request.value = 1 }
composeRule.mainClock.advanceTimeBy(2_000)
composeRule.waitForIdle()
assertEquals("a new request should scroll back to the top", 0, state.firstVisibleItemIndex)
}
}