diff --git a/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt b/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt index 5923a284..54b48899 100644 --- a/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt +++ b/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt @@ -306,17 +306,17 @@ interface LibraryDao { @Query( "SELECT genre FROM (" + - "SELECT song_genres.genre AS genre, songs.played AS played FROM song_genres " + + "SELECT song_genres.genre AS genre, songs.playCount AS plays FROM song_genres " + "JOIN songs ON songs.sourceId = song_genres.sourceId AND songs.id = song_genres.songId " + "WHERE song_genres.sourceId = :sourceId " + "UNION ALL " + - "SELECT songs.genre AS genre, songs.played AS played FROM songs " + + "SELECT songs.genre AS genre, songs.playCount AS plays FROM songs " + "WHERE songs.sourceId = :sourceId AND songs.genre IS NOT NULL AND songs.genre != '' " + "AND NOT EXISTS (SELECT 1 FROM song_genres " + "WHERE song_genres.sourceId = songs.sourceId AND song_genres.songId = songs.id)" + - ") GROUP BY genre ORDER BY MAX(played) DESC NULLS LAST, genre COLLATE NOCASE", + ") GROUP BY genre ORDER BY SUM(plays) DESC, genre COLLATE NOCASE", ) - fun genresByRecentPlay(sourceId: Long): Flow> + fun genresByMostPlayed(sourceId: Long): Flow> @Query( "SELECT DISTINCT (year / 10) * 10 AS decade FROM albums " + diff --git a/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt b/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt index 54daa20b..ffb83998 100644 --- a/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt +++ b/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt @@ -79,7 +79,7 @@ class LibraryRepository( limit: Int, ): Flow> = db.libraryDao().recentlyStarredSongs(sourceId, limit) - fun genresByRecentPlay(sourceId: Long): Flow> = db.libraryDao().genresByRecentPlay(sourceId) + fun genresByMostPlayed(sourceId: Long): Flow> = db.libraryDao().genresByMostPlayed(sourceId) fun decades(sourceId: Long): Flow> = db.libraryDao().decades(sourceId) diff --git a/app/src/main/kotlin/com/subtracks/ui/home/HomeListViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/home/HomeListViewModel.kt index 7f8ab136..83f902c1 100644 --- a/app/src/main/kotlin/com/subtracks/ui/home/HomeListViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/home/HomeListViewModel.kt @@ -97,7 +97,7 @@ class HomeListViewModel( val genres: StateFlow> = sourceId - .flatMapLatest { libraryRepository.genresByRecentPlay(it) } + .flatMapLatest { libraryRepository.genresByMostPlayed(it) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) val decades: StateFlow> = diff --git a/app/src/main/kotlin/com/subtracks/ui/home/HomeScreen.kt b/app/src/main/kotlin/com/subtracks/ui/home/HomeScreen.kt index 2daae559..00f3dc94 100644 --- a/app/src/main/kotlin/com/subtracks/ui/home/HomeScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/home/HomeScreen.kt @@ -3,6 +3,8 @@ package com.subtracks.ui.home import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -246,6 +248,7 @@ private fun HomeSectionHeader( private val TILE_SIZE = 132.dp private val ARTIST_TILE_SIZE = 104.dp private val TILE_SPACING = 8.dp +private val ARTIST_SPACING = 16.dp private fun LazyListScope.albumRow( section: HomeSection, @@ -292,7 +295,7 @@ private fun LazyListScope.artistRow( item(key = "${section.name}-row") { LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(TILE_SPACING), + horizontalArrangement = Arrangement.spacedBy(ARTIST_SPACING), ) { items(artists, key = { it.id }) { artist -> val ref = coverArt(artist.coverArt, true) @@ -307,6 +310,7 @@ private fun LazyListScope.artistRow( } } +@OptIn(ExperimentalLayoutApi::class) private fun LazyListScope.genreBlock( section: HomeSection, genres: List, @@ -318,16 +322,13 @@ private fun LazyListScope.genreBlock( HomeSectionHeader(section.title) { onMore(section) } } item(key = "${section.name}-row") { - LazyRow( - contentPadding = PaddingValues(horizontal = 16.dp), + FlowRow( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(TILE_SPACING), + verticalArrangement = Arrangement.spacedBy(TILE_SPACING), ) { - items(genres.chunked(GENRE_COLUMN_HEIGHT), key = { it.first() }) { column -> - Column(verticalArrangement = Arrangement.spacedBy(TILE_SPACING)) { - column.forEach { genre -> - AssistChip(onClick = { onGenreClick(genre) }, label = { Text(genre) }) - } - } + genres.forEach { genre -> + AssistChip(onClick = { onGenreClick(genre) }, label = { Text(genre) }) } } } @@ -384,8 +385,6 @@ private fun LazyListScope.starredSongs( } } -private const val GENRE_COLUMN_HEIGHT = 3 - @Composable private fun AlbumTile( album: Album, @@ -399,7 +398,7 @@ private fun AlbumTile( CoverArt( ref = ref, name = album.name, - modifier = Modifier.size(TILE_SIZE).clip(RoundedCornerShape(4.dp)), + modifier = Modifier.size(TILE_SIZE).clip(RoundedCornerShape(2.dp)), ) Spacer(Modifier.height(6.dp)) Text( @@ -427,7 +426,7 @@ private fun ArtistTile( ) { Column( modifier = Modifier.width(ARTIST_TILE_SIZE).combinedClickable(onClick = onClick, onLongClick = onLongClick), - horizontalAlignment = Alignment.CenterHorizontally, + horizontalAlignment = Alignment.Start, ) { CoverArt( ref = ref, diff --git a/app/src/main/kotlin/com/subtracks/ui/home/HomeViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/home/HomeViewModel.kt index 5192506d..3989d8fa 100644 --- a/app/src/main/kotlin/com/subtracks/ui/home/HomeViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/home/HomeViewModel.kt @@ -67,7 +67,7 @@ class HomeViewModel( } val discovery = combine( - libraryRepository.genresByRecentPlay(sourceId), + libraryRepository.genresByMostPlayed(sourceId), libraryRepository.decades(sourceId), libraryRepository.recentlyStarredSongs(sourceId, HOME_STARRED_LIMIT), libraryRepository.recentlyAddedAlbums(sourceId, HOME_ROW_LIMIT), diff --git a/app/src/test/kotlin/com/subtracks/data/db/LibraryHomeDaoTest.kt b/app/src/test/kotlin/com/subtracks/data/db/LibraryHomeDaoTest.kt index 3475a826..6681fca6 100644 --- a/app/src/test/kotlin/com/subtracks/data/db/LibraryHomeDaoTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/db/LibraryHomeDaoTest.kt @@ -124,22 +124,22 @@ class LibraryHomeDaoTest { } @Test - fun genresOrderByMostRecentPlayWithinTheGenre() = + fun genresOrderByMostPlayed() = runTest { val sourceId = source() val dao = db.libraryDao() dao.upsertSongs( listOf( - song(sourceId, "s1", "One", genre = "Rock", played = 100), - song(sourceId, "s2", "Two", genre = "Jazz", played = 400), - song(sourceId, "s3", "Three", genre = "Rock", played = 300), - song(sourceId, "s4", "Four", genre = "Classical", played = null), + song(sourceId, "s1", "One", genre = "Rock", playCount = 5), + song(sourceId, "s2", "Two", genre = "Jazz", playCount = 2), + song(sourceId, "s3", "Three", genre = "Rock", playCount = 4), + song(sourceId, "s4", "Four", genre = "Classical", playCount = 0), ), ) assertEquals( - listOf("Jazz", "Rock", "Classical"), - dao.genresByRecentPlay(sourceId).first(), + listOf("Rock", "Jazz", "Classical"), + dao.genresByMostPlayed(sourceId).first(), ) } @@ -150,8 +150,8 @@ class LibraryHomeDaoTest { val dao = db.libraryDao() dao.upsertSongs( listOf( - song(sourceId, "s1", "One", genre = "Legacy", played = 100), - song(sourceId, "s2", "Two", genre = "Legacy", played = 300), + song(sourceId, "s1", "One", genre = "Legacy", playCount = 1), + song(sourceId, "s2", "Two", genre = "Legacy", playCount = 3), ), ) insertGenre(sourceId, "s2", 0, "Rock") @@ -159,7 +159,7 @@ class LibraryHomeDaoTest { assertEquals( listOf("Electronic", "Rock", "Legacy"), - dao.genresByRecentPlay(sourceId).first(), + dao.genresByMostPlayed(sourceId).first(), ) } diff --git a/app/src/test/kotlin/com/subtracks/ui/home/HomeScreenshotTest.kt b/app/src/test/kotlin/com/subtracks/ui/home/HomeScreenshotTest.kt index dbad054e..04a39a2c 100644 --- a/app/src/test/kotlin/com/subtracks/ui/home/HomeScreenshotTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/home/HomeScreenshotTest.kt @@ -7,6 +7,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule 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.test.ext.junit.runners.AndroidJUnit4 import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers import com.github.takahirom.roborazzi.captureRoboImage @@ -48,6 +50,31 @@ class HomeScreenshotTest { composeRule.onRoot().captureRoboImage("src/test/screenshots/home.png") } + @Test + fun homeGenres() { + composeRule.setContent { + SubtracksTheme { + Surface(modifier = Modifier.fillMaxSize()) { + HomeScreen( + feed = feed(), + coverArt = { _, _ -> null }, + playingSongId = null, + ) + } + } + } + composeRule.waitUntil(timeoutMillis = 5_000) { + composeRule.onAllNodesWithText("Recently added").fetchSemanticsNodes().isNotEmpty() + } + repeat(2) { + composeRule.onRoot().performTouchInput { + swipeUp(startY = centerY + 600f, endY = centerY - 600f, durationMillis = 300) + } + composeRule.waitForIdle() + } + composeRule.onRoot().captureRoboImage("src/test/screenshots/home_genres.png") + } + private fun feed() = HomeFeed( recentlyPlayedAlbums = albums("Recent"),