mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 23:21:59 +01:00
show disc headings in multi-disc album track lists
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package com.subtracks.ui.library
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.subtracks.data.model.Album
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
@@ -64,6 +67,7 @@ fun AlbumDetailScreen(
|
||||
playingSongId: String? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val multiDisc = songs.map { it.disc ?: 1L }.distinct().size > 1
|
||||
HeroDetailScaffold(
|
||||
artwork = artwork,
|
||||
title = album?.name.orEmpty(),
|
||||
@@ -88,14 +92,32 @@ fun AlbumDetailScreen(
|
||||
)
|
||||
},
|
||||
content = { rowModifier ->
|
||||
itemsIndexed(songs, key = { _, song -> song.id }) { index, song ->
|
||||
SongRow(
|
||||
song = song,
|
||||
isPlaying = song.id == playingSongId,
|
||||
modifier = rowModifier.clickable { onSongClick(index) },
|
||||
)
|
||||
var lastDisc: Long? = null
|
||||
songs.forEachIndexed { index, song ->
|
||||
val disc = song.disc ?: 1L
|
||||
if (multiDisc && disc != lastDisc) {
|
||||
item(key = "disc:$disc") { DiscHeader(disc) }
|
||||
}
|
||||
lastDisc = disc
|
||||
item(key = song.id) {
|
||||
SongRow(
|
||||
song = song,
|
||||
isPlaying = song.id == playingSongId,
|
||||
modifier = rowModifier.clickable { onSongClick(index) },
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DiscHeader(disc: Long) {
|
||||
Text(
|
||||
text = "Disc $disc",
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 20.dp, bottom = 4.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.subtracks.ui
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers
|
||||
import com.subtracks.data.model.Album
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.ui.library.AlbumDetailScreen
|
||||
import com.subtracks.ui.theme.SubtracksTheme
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
@Config(qualifiers = RobolectricDeviceQualifiers.PixelXL)
|
||||
class AlbumDiscsTest {
|
||||
@get:Rule
|
||||
val composeRule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun multiDiscAlbumShowsDiscHeaders() {
|
||||
setContent(listOf(song("s-a", disc = 1, track = 1), song("s-b", disc = 1, track = 2), song("s-c", disc = 2, track = 1)))
|
||||
assertEquals(1, nodesWithText("Disc 1"))
|
||||
assertEquals(1, nodesWithText("Disc 2"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun singleDiscAlbumShowsNoDiscHeaders() {
|
||||
setContent(listOf(song("s-a", disc = 1, track = 1), song("s-b", disc = 1, track = 2)))
|
||||
assertEquals(0, nodesWithText("Disc 1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun discHeadersAreHiddenWhenDiscsAreMissing() {
|
||||
setContent(listOf(song("s-a", disc = null, track = 1), song("s-b", disc = null, track = 2)))
|
||||
assertEquals(0, nodesWithText("Disc 1"))
|
||||
}
|
||||
|
||||
private fun setContent(songs: List<Song>) {
|
||||
composeRule.setContent {
|
||||
SubtracksTheme {
|
||||
AlbumDetailScreen(
|
||||
album = album(),
|
||||
songs = songs,
|
||||
coverArt = { _, _ -> null },
|
||||
artwork = null,
|
||||
onBack = {},
|
||||
onSongClick = {},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
composeRule.waitUntil(timeoutMillis = 5_000) { nodesWithText(songs.first().title) == 1 }
|
||||
}
|
||||
|
||||
private fun nodesWithText(text: String) = composeRule.onAllNodesWithText(text).fetchSemanticsNodes().size
|
||||
|
||||
private fun song(
|
||||
id: String,
|
||||
disc: Long?,
|
||||
track: Long,
|
||||
) = Song(
|
||||
sourceId = 1,
|
||||
id = id,
|
||||
albumId = "al-1",
|
||||
artistId = "ar-1",
|
||||
title = "Song $id",
|
||||
album = "Album",
|
||||
artist = "Artist",
|
||||
duration = 200,
|
||||
track = track,
|
||||
disc = disc,
|
||||
starred = null,
|
||||
genre = null,
|
||||
)
|
||||
|
||||
private fun album() =
|
||||
Album(
|
||||
sourceId = 1,
|
||||
id = "al-1",
|
||||
artistId = "ar-1",
|
||||
name = "Album",
|
||||
albumArtist = "Artist",
|
||||
created = 0,
|
||||
coverArt = null,
|
||||
genre = null,
|
||||
year = null,
|
||||
starred = null,
|
||||
songCount = 3,
|
||||
frequentRank = null,
|
||||
recentRank = null,
|
||||
)
|
||||
}
|
||||
@@ -15,6 +15,8 @@ import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onAllNodesWithTag
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.compose.ui.test.swipeUp
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
@@ -155,6 +157,36 @@ class AppScreenshotTest {
|
||||
composeRule.onRoot().captureRoboImage("src/test/screenshots/album_detail_cool.png")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun albumDetailMultiDisc() {
|
||||
val songs =
|
||||
listOf(
|
||||
Fixtures.song("s-eiirp", "al-kid-a", "ar-radiohead", "Everything In Its Right Place", "Kid A", "Radiohead", 1, 251),
|
||||
Fixtures.song("s-kid-a", "al-kid-a", "ar-radiohead", "Kid A", "Kid A", "Radiohead", 2, 274),
|
||||
Fixtures.song("s-anthem", "al-kid-a", "ar-radiohead", "The National Anthem", "Kid A", "Radiohead", 1, 351, disc = 2),
|
||||
Fixtures.song("s-htdc", "al-kid-a", "ar-radiohead", "How to Disappear Completely", "Kid A", "Radiohead", 2, 356, disc = 2),
|
||||
)
|
||||
composeRule.setContent {
|
||||
SubtracksTheme {
|
||||
AlbumDetailScreen(
|
||||
album = Fixtures.albums.first(),
|
||||
songs = songs,
|
||||
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
|
||||
artwork = artworkColorsFromSeed(Color.rgb(120, 80, 200)),
|
||||
onBack = {},
|
||||
onSongClick = {},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
awaitText("Everything In Its Right Place")
|
||||
composeRule.onRoot().performTouchInput { swipeUp(startY = centerY + 600f, endY = centerY - 600f, durationMillis = 400) }
|
||||
composeRule.waitUntil(timeoutMillis = 5_000) {
|
||||
composeRule.onAllNodesWithText("Disc 2").fetchSemanticsNodes().isNotEmpty()
|
||||
}
|
||||
composeRule.onRoot().captureRoboImage("src/test/screenshots/album_detail_multidisc.png")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun artistDetail() {
|
||||
composeRule.setContent {
|
||||
@@ -489,7 +521,7 @@ private object Fixtures {
|
||||
recentRank = null,
|
||||
)
|
||||
|
||||
private fun song(
|
||||
fun song(
|
||||
id: String,
|
||||
albumId: String,
|
||||
artistId: String,
|
||||
@@ -498,6 +530,7 @@ private object Fixtures {
|
||||
artist: String,
|
||||
track: Long,
|
||||
duration: Long,
|
||||
disc: Long? = 1,
|
||||
) = Song(
|
||||
sourceId = 1,
|
||||
id = id,
|
||||
@@ -508,7 +541,7 @@ private object Fixtures {
|
||||
artist = artist,
|
||||
duration = duration,
|
||||
track = track,
|
||||
disc = 1,
|
||||
disc = disc,
|
||||
starred = null,
|
||||
genre = null,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user