diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt index e13bcbb..a2d2d08 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/Database.kt @@ -38,10 +38,10 @@ import it.vfsfitvnm.vimusic.enums.SongSortBy import it.vfsfitvnm.vimusic.enums.SortOrder import it.vfsfitvnm.vimusic.models.Album import it.vfsfitvnm.vimusic.models.Artist -import it.vfsfitvnm.vimusic.models.DetailedSong -import it.vfsfitvnm.vimusic.models.DetailedSongWithContentLength +import it.vfsfitvnm.vimusic.models.SongWithContentLength import it.vfsfitvnm.vimusic.models.Event import it.vfsfitvnm.vimusic.models.Format +import it.vfsfitvnm.vimusic.models.Info import it.vfsfitvnm.vimusic.models.Playlist import it.vfsfitvnm.vimusic.models.PlaylistPreview import it.vfsfitvnm.vimusic.models.PlaylistWithSongs @@ -62,34 +62,34 @@ interface Database { @Transaction @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY ROWID ASC") @RewriteQueriesToDropUnusedColumns - fun songsByRowIdAsc(): Flow> + fun songsByRowIdAsc(): Flow> @Transaction @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY ROWID DESC") @RewriteQueriesToDropUnusedColumns - fun songsByRowIdDesc(): Flow> + fun songsByRowIdDesc(): Flow> @Transaction @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY title ASC") @RewriteQueriesToDropUnusedColumns - fun songsByTitleAsc(): Flow> + fun songsByTitleAsc(): Flow> @Transaction @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY title DESC") @RewriteQueriesToDropUnusedColumns - fun songsByTitleDesc(): Flow> + fun songsByTitleDesc(): Flow> @Transaction @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY totalPlayTimeMs ASC") @RewriteQueriesToDropUnusedColumns - fun songsByPlayTimeAsc(): Flow> + fun songsByPlayTimeAsc(): Flow> @Transaction @Query("SELECT * FROM Song WHERE totalPlayTimeMs > 0 ORDER BY totalPlayTimeMs DESC") @RewriteQueriesToDropUnusedColumns - fun songsByPlayTimeDesc(): Flow> + fun songsByPlayTimeDesc(): Flow> - fun songs(sortBy: SongSortBy, sortOrder: SortOrder): Flow> { + fun songs(sortBy: SongSortBy, sortOrder: SortOrder): Flow> { return when (sortBy) { SongSortBy.PlayTime -> when (sortOrder) { SortOrder.Ascending -> songsByPlayTimeAsc() @@ -109,7 +109,7 @@ interface Database { @Transaction @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY likedAt DESC") @RewriteQueriesToDropUnusedColumns - fun favorites(): Flow> + fun favorites(): Flow> @Query("SELECT * FROM QueuedMediaItem") fun queue(): List @@ -187,7 +187,7 @@ interface Database { @Transaction @Query("SELECT * FROM Song JOIN SongAlbumMap ON Song.id = SongAlbumMap.songId WHERE SongAlbumMap.albumId = :albumId AND position IS NOT NULL ORDER BY position") @RewriteQueriesToDropUnusedColumns - fun albumSongs(albumId: String): Flow> + fun albumSongs(albumId: String): Flow> @Query("SELECT * FROM Album WHERE bookmarkedAt IS NOT NULL ORDER BY title ASC") fun albumsByTitleAsc(): Flow> @@ -281,15 +281,14 @@ interface Database { @Transaction @Query("SELECT * FROM Song JOIN SongArtistMap ON Song.id = SongArtistMap.songId WHERE SongArtistMap.artistId = :artistId AND totalPlayTimeMs > 0 ORDER BY Song.ROWID DESC") @RewriteQueriesToDropUnusedColumns - fun artistSongs(artistId: String): Flow> + fun artistSongs(artistId: String): Flow> @Query("SELECT * FROM Format WHERE songId = :songId") fun format(songId: String): Flow @Transaction - @Query("SELECT * FROM Song JOIN Format ON id = songId WHERE contentLength IS NOT NULL AND totalPlayTimeMs > 0 ORDER BY Song.ROWID DESC") - @RewriteQueriesToDropUnusedColumns - fun songsWithContentLength(): Flow> + @Query("SELECT Song.*, contentLength FROM Song JOIN Format ON id = songId WHERE contentLength IS NOT NULL AND totalPlayTimeMs > 0 ORDER BY Song.ROWID DESC") + fun songsWithContentLength(): Flow> @Query(""" UPDATE SongPlaylistMap SET position = @@ -312,12 +311,18 @@ interface Database { fun loudnessDb(songId: String): Flow @Query("SELECT * FROM Song WHERE title LIKE :query OR artistsText LIKE :query") - fun search(query: String): Flow> + fun search(query: String): Flow> + + @Query("SELECT albumId AS id, NULL AS name FROM SongAlbumMap WHERE songId = :songId") + fun songAlbumInfo(songId: String): Info + + @Query("SELECT id, name FROM Artist LEFT JOIN SongArtistMap ON id = artistId WHERE songId = :songId") + fun songArtistInfo(songId: String): List @Transaction @Query("SELECT Song.* FROM Event JOIN Song ON Song.id = songId GROUP BY songId ORDER BY SUM(CAST(playTime AS REAL) / (((:now - timestamp) / 86400000) + 1)) DESC LIMIT 1") @RewriteQueriesToDropUnusedColumns - fun trending(now: Long = System.currentTimeMillis()): Flow + fun trending(now: Long = System.currentTimeMillis()): Flow @Query("SELECT COUNT (*) FROM Event") fun eventsCount(): Flow diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/DetailedSong.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/DetailedSong.kt deleted file mode 100644 index 13d6051..0000000 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/DetailedSong.kt +++ /dev/null @@ -1,47 +0,0 @@ -package it.vfsfitvnm.vimusic.models - -import androidx.compose.runtime.Immutable -import androidx.room.Junction -import androidx.room.Relation - -@Immutable -open class DetailedSong( - val id: String, - val title: String, - val artistsText: String? = null, - val durationText: String?, - val thumbnailUrl: String?, - val totalPlayTimeMs: Long = 0, - @Relation( - entity = SongAlbumMap::class, - entityColumn = "songId", - parentColumn = "id", - projection = ["albumId"] - ) - val albumId: String?, - @Relation( - entity = Artist::class, - entityColumn = "id", - parentColumn = "id", - associateBy = Junction( - value = SongArtistMap::class, - parentColumn = "songId", - entityColumn = "artistId" - ), - projection = ["id", "name"] - ) - val artists: List? -) { - val formattedTotalPlayTime: String - get() { - val seconds = totalPlayTimeMs / 1000 - - val hours = seconds / 3600 - - return when { - hours == 0L -> "${seconds / 60}m" - hours < 24L -> "${hours}h" - else -> "${hours / 24}d" - } - } -} diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/DetailedSongWithContentLength.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/DetailedSongWithContentLength.kt deleted file mode 100644 index 71ef0bd..0000000 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/DetailedSongWithContentLength.kt +++ /dev/null @@ -1,23 +0,0 @@ -package it.vfsfitvnm.vimusic.models - -import androidx.compose.runtime.Immutable -import androidx.room.Relation - -@Immutable -class DetailedSongWithContentLength( - id: String, - title: String, - artistsText: String? = null, - durationText: String?, - thumbnailUrl: String?, - totalPlayTimeMs: Long = 0, - albumId: String?, - artists: List?, - @Relation( - entity = Format::class, - entityColumn = "songId", - parentColumn = "id", - projection = ["contentLength"] - ) - val contentLength: Long? -) : DetailedSong(id, title, artistsText, durationText, thumbnailUrl, totalPlayTimeMs, albumId, artists) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/PlaylistWithSongs.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/PlaylistWithSongs.kt index ac26fda..27d8f9a 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/PlaylistWithSongs.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/PlaylistWithSongs.kt @@ -18,5 +18,5 @@ data class PlaylistWithSongs( entityColumn = "songId" ) ) - val songs: List + val songs: List ) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/Song.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/Song.kt index f29e138..909e239 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/Song.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/Song.kt @@ -17,6 +17,19 @@ data class Song( val likedAt: Long? = null, val totalPlayTimeMs: Long = 0 ) { + val formattedTotalPlayTime: String + get() { + val seconds = totalPlayTimeMs / 1000 + + val hours = seconds / 3600 + + return when { + hours == 0L -> "${seconds / 60}m" + hours < 24L -> "${hours}h" + else -> "${hours / 24}d" + } + } + fun toggleLike(): Song { return copy( likedAt = if (likedAt == null) System.currentTimeMillis() else null diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/SongWithContentLength.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/SongWithContentLength.kt new file mode 100644 index 0000000..e43d56e --- /dev/null +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/SongWithContentLength.kt @@ -0,0 +1,10 @@ +package it.vfsfitvnm.vimusic.models + +import androidx.compose.runtime.Immutable +import androidx.room.Embedded + +@Immutable +data class SongWithContentLength( + @Embedded val song: Song, + val contentLength: Long? +) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerMediaBrowserService.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerMediaBrowserService.kt index 5b1907a..c97cdcf 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerMediaBrowserService.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerMediaBrowserService.kt @@ -20,8 +20,9 @@ import androidx.media3.datasource.cache.Cache import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.models.Album -import it.vfsfitvnm.vimusic.models.DetailedSong import it.vfsfitvnm.vimusic.models.PlaylistPreview +import it.vfsfitvnm.vimusic.models.Song +import it.vfsfitvnm.vimusic.models.SongWithContentLength import it.vfsfitvnm.vimusic.utils.asMediaItem import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex import it.vfsfitvnm.vimusic.utils.forceSeekToNext @@ -36,7 +37,7 @@ import kotlinx.coroutines.withContext class PlayerMediaBrowserService : MediaBrowserService(), ServiceConnection { private val coroutineScope = CoroutineScope(Dispatchers.IO) - private var lastSongs = emptyList() + private var lastSongs = emptyList() private var bound = false @@ -187,7 +188,7 @@ class PlayerMediaBrowserService : MediaBrowserService(), ServiceConnection { BrowserMediaItem.FLAG_PLAYABLE ) - private val DetailedSong.asBrowserMediaItem + private val Song.asBrowserMediaItem inline get() = BrowserMediaItem( BrowserMediaDescription.Builder() .setMediaId(MediaId.forSong(id)) @@ -254,9 +255,10 @@ class PlayerMediaBrowserService : MediaBrowserService(), ServiceConnection { .first() .filter { song -> song.contentLength?.let { - cache.isCached(song.id, 0, song.contentLength) + cache.isCached(song.song.id, 0, it) } ?: false } + .map(SongWithContentLength::song) .shuffled() MediaId.playlists -> data @@ -273,7 +275,7 @@ class PlayerMediaBrowserService : MediaBrowserService(), ServiceConnection { ?.first() else -> emptyList() - }?.map(DetailedSong::asMediaItem) ?: return@launch + }?.map(Song::asMediaItem) ?: return@launch withContext(Dispatchers.Main) { player.forcePlayAtIndex(mediaItems, index.coerceIn(0, mediaItems.size)) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/MediaItemMenu.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/MediaItemMenu.kt index 40982ea..5bc370d 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/MediaItemMenu.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/components/themed/MediaItemMenu.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -48,7 +49,7 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.enums.PlaylistSortBy import it.vfsfitvnm.vimusic.enums.SortOrder -import it.vfsfitvnm.vimusic.models.DetailedSong +import it.vfsfitvnm.vimusic.models.Info import it.vfsfitvnm.vimusic.models.Playlist import it.vfsfitvnm.vimusic.models.Song import it.vfsfitvnm.vimusic.models.SongPlaylistMap @@ -69,15 +70,16 @@ import it.vfsfitvnm.vimusic.utils.formatAsDuration import it.vfsfitvnm.vimusic.utils.medium import it.vfsfitvnm.vimusic.utils.semiBold import it.vfsfitvnm.vimusic.utils.thumbnail +import kotlin.system.measureTimeMillis import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.withContext @ExperimentalAnimationApi @Composable fun InHistoryMediaItemMenu( onDismiss: () -> Unit, - song: DetailedSong, + song: Song, modifier: Modifier = Modifier ) { val binder = LocalPlayerServiceBinder.current @@ -115,7 +117,7 @@ fun InPlaylistMediaItemMenu( onDismiss: () -> Unit, playlistId: Long, positionInPlaylist: Int, - song: DetailedSong, + song: Song, modifier: Modifier = Modifier ) { NonQueuedMediaItemMenu( @@ -276,15 +278,43 @@ fun MediaItemMenu( mutableStateOf(0.dp) } - val likedAt by remember(mediaItem.mediaId) { - Database.likedAt(mediaItem.mediaId).distinctUntilChanged() - }.collectAsState(initial = null, context = Dispatchers.IO) + var albumInfo by remember { + mutableStateOf(mediaItem.mediaMetadata.extras?.getString("albumId")?.let { albumId -> + Info(albumId, null) + }) + } + + var artistsInfo by remember { + mutableStateOf( + mediaItem.mediaMetadata.extras?.getStringArrayList("artistNames")?.let { artistNames -> + mediaItem.mediaMetadata.extras?.getStringArrayList("artistIds")?.let { artistIds -> + artistNames.zip(artistIds).map { (authorName, authorId) -> + Info(authorId, authorName) + } + } + } + ) + } + + var likedAt by remember { + mutableStateOf(null) + } + + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + if (albumInfo == null) albumInfo = Database.songAlbumInfo(mediaItem.mediaId) + if (artistsInfo == null) artistsInfo = Database.songArtistInfo(mediaItem.mediaId) + + Database.likedAt(mediaItem.mediaId).collect { likedAt = it } + } + } AnimatedContent( targetState = isViewingPlaylists, transitionSpec = { val animationSpec = tween(400) - val slideDirection = if (targetState) AnimatedContentScope.SlideDirection.Left else AnimatedContentScope.SlideDirection.Right + val slideDirection = + if (targetState) AnimatedContentScope.SlideDirection.Left else AnimatedContentScope.SlideDirection.Right slideIntoContainer(slideDirection, animationSpec) with slideOutOfContainer(slideDirection, animationSpec) @@ -371,7 +401,8 @@ fun MediaItemMenu( .padding(end = 12.dp) ) { SongItem( - thumbnailUrl = mediaItem.mediaMetadata.artworkUri.thumbnail(thumbnailSizePx)?.toString(), + thumbnailUrl = mediaItem.mediaMetadata.artworkUri.thumbnail(thumbnailSizePx) + ?.toString(), title = mediaItem.mediaMetadata.title.toString(), authors = mediaItem.mediaMetadata.artist.toString(), duration = null, @@ -601,7 +632,10 @@ fun MediaItemMenu( text = "${formatAsDuration(it)} left", style = typography.xxs.medium, modifier = modifier - .background(color = colorPalette.background0, shape = RoundedCornerShape(16.dp)) + .background( + color = colorPalette.background0, + shape = RoundedCornerShape(16.dp) + ) .padding(horizontal = 16.dp, vertical = 8.dp) .animateContentSize() ) @@ -619,7 +653,9 @@ fun MediaItemMenu( Image( painter = painterResource(R.drawable.chevron_forward), contentDescription = null, - colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(colorPalette.textSecondary), + colorFilter = androidx.compose.ui.graphics.ColorFilter.tint( + colorPalette.textSecondary + ), modifier = Modifier .size(16.dp) ) @@ -628,7 +664,7 @@ fun MediaItemMenu( } onGoToAlbum?.let { onGoToAlbum -> - mediaItem.mediaMetadata.extras?.getString("albumId")?.let { albumId -> + albumInfo?.let { (albumId) -> MenuEntry( icon = R.drawable.disc, text = "Go to album", @@ -641,25 +677,16 @@ fun MediaItemMenu( } onGoToArtist?.let { onGoToArtist -> - mediaItem.mediaMetadata.extras?.getStringArrayList("artistNames") - ?.let { artistNames -> - mediaItem.mediaMetadata.extras?.getStringArrayList("artistIds") - ?.let { artistIds -> - artistNames.zip(artistIds) - .forEach { (authorName, authorId) -> - if (authorId != null) { - MenuEntry( - icon = R.drawable.person, - text = "More of $authorName", - onClick = { - onDismiss() - onGoToArtist(authorId) - } - ) - } - } - } - } + artistsInfo?.forEach { (authorId, authorName) -> + MenuEntry( + icon = R.drawable.person, + text = "More of $authorName", + onClick = { + onDismiss() + onGoToArtist(authorId) + } + ) + } } onRemoveFromQueue?.let { onRemoveFromQueue -> diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/SongItem.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/SongItem.kt index 3395ec8..f9a6cea 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/SongItem.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/SongItem.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.media3.common.MediaItem import coil.compose.AsyncImage -import it.vfsfitvnm.vimusic.models.DetailedSong import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance import it.vfsfitvnm.vimusic.ui.styling.shimmer @@ -28,6 +27,7 @@ import it.vfsfitvnm.vimusic.utils.secondary import it.vfsfitvnm.vimusic.utils.semiBold import it.vfsfitvnm.vimusic.utils.thumbnail import it.vfsfitvnm.innertube.Innertube +import it.vfsfitvnm.vimusic.models.Song @Composable fun SongItem( @@ -69,7 +69,7 @@ fun SongItem( @Composable fun SongItem( - song: DetailedSong, + song: Song, thumbnailSizePx: Int, thumbnailSizeDp: Dp, modifier: Modifier = Modifier, diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumSongs.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumSongs.kt index 3d08043..ae0352e 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumSongs.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumSongs.kt @@ -27,7 +27,7 @@ import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.R -import it.vfsfitvnm.vimusic.models.DetailedSong +import it.vfsfitvnm.vimusic.models.Song import it.vfsfitvnm.vimusic.ui.components.LocalMenuState import it.vfsfitvnm.vimusic.ui.components.ShimmerHost import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop @@ -59,7 +59,7 @@ fun AlbumSongs( val binder = LocalPlayerServiceBinder.current val menuState = LocalMenuState.current - var songs by persistList("album/$browseId/songs") + var songs by persistList("album/$browseId/songs") LaunchedEffect(Unit) { Database.albumSongs(browseId).collect { songs = it } @@ -89,7 +89,7 @@ fun AlbumSongs( text = "Enqueue", enabled = songs.isNotEmpty(), onClick = { - binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem)) + binder?.player?.enqueue(songs.map(Song::asMediaItem)) } ) } @@ -133,7 +133,7 @@ fun AlbumSongs( onClick = { binder?.stopRadio() binder?.player?.forcePlayAtIndex( - songs.map(DetailedSong::asMediaItem), + songs.map(Song::asMediaItem), index ) } @@ -162,7 +162,7 @@ fun AlbumSongs( if (songs.isNotEmpty()) { binder?.stopRadio() binder?.player?.forcePlayFromBeginning( - songs.shuffled().map(DetailedSong::asMediaItem) + songs.shuffled().map(Song::asMediaItem) ) } } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/artist/ArtistLocalSongs.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/artist/ArtistLocalSongs.kt index 9d43137..aad59e7 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/artist/ArtistLocalSongs.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/artist/ArtistLocalSongs.kt @@ -24,7 +24,7 @@ import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.R -import it.vfsfitvnm.vimusic.models.DetailedSong +import it.vfsfitvnm.vimusic.models.Song import it.vfsfitvnm.vimusic.ui.components.LocalMenuState import it.vfsfitvnm.vimusic.ui.components.ShimmerHost import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop @@ -53,7 +53,7 @@ fun ArtistLocalSongs( val (colorPalette) = LocalAppearance.current val menuState = LocalMenuState.current - var songs by persist?>("artist/$browseId/localSongs") + var songs by persist?>("artist/$browseId/localSongs") LaunchedEffect(Unit) { Database.artistSongs(browseId).collect { songs = it } @@ -84,7 +84,7 @@ fun ArtistLocalSongs( text = "Enqueue", enabled = !songs.isNullOrEmpty(), onClick = { - binder?.player?.enqueue(songs!!.map(DetailedSong::asMediaItem)) + binder?.player?.enqueue(songs!!.map(Song::asMediaItem)) } ) } @@ -115,7 +115,7 @@ fun ArtistLocalSongs( onClick = { binder?.stopRadio() binder?.player?.forcePlayAtIndex( - songs.map(DetailedSong::asMediaItem), + songs.map(Song::asMediaItem), index ) } @@ -139,7 +139,7 @@ fun ArtistLocalSongs( if (songs.isNotEmpty()) { binder?.stopRadio() binder?.player?.forcePlayFromBeginning( - songs.shuffled().map(DetailedSong::asMediaItem) + songs.shuffled().map(Song::asMediaItem) ) } } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/builtinplaylist/BuiltInPlaylistSongs.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/builtinplaylist/BuiltInPlaylistSongs.kt index 29ba524..546625e 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/builtinplaylist/BuiltInPlaylistSongs.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/builtinplaylist/BuiltInPlaylistSongs.kt @@ -26,7 +26,8 @@ import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist -import it.vfsfitvnm.vimusic.models.DetailedSong +import it.vfsfitvnm.vimusic.models.Song +import it.vfsfitvnm.vimusic.models.SongWithContentLength import it.vfsfitvnm.vimusic.ui.components.LocalMenuState import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop import it.vfsfitvnm.vimusic.ui.components.themed.Header @@ -53,7 +54,7 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) { val binder = LocalPlayerServiceBinder.current val menuState = LocalMenuState.current - var songs by persistList("${builtInPlaylist.name}/songs") + var songs by persistList("${builtInPlaylist.name}/songs") LaunchedEffect(Unit) { when (builtInPlaylist) { @@ -66,9 +67,9 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) { .map { songs -> songs.filter { song -> song.contentLength?.let { - binder?.cache?.isCached(song.id, 0, song.contentLength) + binder?.cache?.isCached(song.song.id, 0, song.contentLength) } ?: false - } + }.map(SongWithContentLength::song) } }.collect { songs = it } } @@ -103,7 +104,7 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) { text = "Enqueue", enabled = songs.isNotEmpty(), onClick = { - binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem)) + binder?.player?.enqueue(songs.map(Song::asMediaItem)) } ) @@ -143,7 +144,7 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) { onClick = { binder?.stopRadio() binder?.player?.forcePlayAtIndex( - songs.map(DetailedSong::asMediaItem), + songs.map(Song::asMediaItem), index ) } @@ -160,7 +161,7 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) { if (songs.isNotEmpty()) { binder?.stopRadio() binder?.player?.forcePlayFromBeginning( - songs.shuffled().map(DetailedSong::asMediaItem) + songs.shuffled().map(Song::asMediaItem) ) } } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeSongs.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeSongs.kt index 2c30448..f395798 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeSongs.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeSongs.kt @@ -38,7 +38,8 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.R import it.vfsfitvnm.vimusic.enums.SongSortBy import it.vfsfitvnm.vimusic.enums.SortOrder -import it.vfsfitvnm.vimusic.models.DetailedSong +import it.vfsfitvnm.vimusic.models.Song +import it.vfsfitvnm.vimusic.query import it.vfsfitvnm.vimusic.ui.components.LocalMenuState import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop import it.vfsfitvnm.vimusic.ui.components.themed.Header @@ -58,6 +59,8 @@ import it.vfsfitvnm.vimusic.utils.rememberPreference import it.vfsfitvnm.vimusic.utils.semiBold import it.vfsfitvnm.vimusic.utils.songSortByKey import it.vfsfitvnm.vimusic.utils.songSortOrderKey +import kotlin.system.measureTimeMillis +import kotlinx.coroutines.flow.first @ExperimentalFoundationApi @ExperimentalAnimationApi @@ -75,9 +78,12 @@ fun HomeSongs( var sortBy by rememberPreference(songSortByKey, SongSortBy.DateAdded) var sortOrder by rememberPreference(songSortOrderKey, SortOrder.Descending) - var items by persistList("home/songs") + var items by persistList("home/songs") LaunchedEffect(sortBy, sortOrder) { + // 670, 58, 97, 91, 94 + println("took ${measureTimeMillis { Database.songs(sortBy, sortOrder).first() }}ms") + Database.songs(sortBy, sortOrder).collect { items = it } } @@ -175,7 +181,7 @@ fun HomeSongs( onClick = { binder?.stopRadio() binder?.player?.forcePlayAtIndex( - items.map(DetailedSong::asMediaItem), + items.map(Song::asMediaItem), index ) } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/QuickPicks.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/QuickPicks.kt index 21abf45..37f6ff6 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/QuickPicks.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/QuickPicks.kt @@ -47,7 +47,7 @@ import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.R -import it.vfsfitvnm.vimusic.models.DetailedSong +import it.vfsfitvnm.vimusic.models.Song import it.vfsfitvnm.vimusic.query import it.vfsfitvnm.vimusic.ui.components.LocalMenuState import it.vfsfitvnm.vimusic.ui.components.ShimmerHost @@ -89,7 +89,7 @@ fun QuickPicks( val menuState = LocalMenuState.current val windowInsets = LocalPlayerAwareWindowInsets.current - var trending by persist("home/trending") + var trending by persist("home/trending") var relatedPageResult by persist?>(tag = "home/relatedPageResult") diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistSongs.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistSongs.kt index 4feaebe..ad18145 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistSongs.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistSongs.kt @@ -36,8 +36,8 @@ import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder import it.vfsfitvnm.vimusic.R -import it.vfsfitvnm.vimusic.models.DetailedSong import it.vfsfitvnm.vimusic.models.PlaylistWithSongs +import it.vfsfitvnm.vimusic.models.Song import it.vfsfitvnm.vimusic.models.SongPlaylistMap import it.vfsfitvnm.vimusic.query import it.vfsfitvnm.vimusic.transaction @@ -158,7 +158,7 @@ fun LocalPlaylistSongs( enabled = playlistWithSongs?.songs?.isNotEmpty() == true, onClick = { playlistWithSongs?.songs - ?.map(DetailedSong::asMediaItem) + ?.map(Song::asMediaItem) ?.let { mediaItems -> binder?.player?.enqueue(mediaItems) } @@ -266,7 +266,7 @@ fun LocalPlaylistSongs( }, onClick = { playlistWithSongs?.songs - ?.map(DetailedSong::asMediaItem) + ?.map(Song::asMediaItem) ?.let { mediaItems -> binder?.stopRadio() binder?.player?.forcePlayAtIndex(mediaItems, index) @@ -288,7 +288,7 @@ fun LocalPlaylistSongs( if (songs.isNotEmpty()) { binder?.stopRadio() binder?.player?.forcePlayFromBeginning( - songs.shuffled().map(DetailedSong::asMediaItem) + songs.shuffled().map(Song::asMediaItem) ) } } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/LocalSongSearch.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/LocalSongSearch.kt index 569b061..4d32ea9 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/LocalSongSearch.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/LocalSongSearch.kt @@ -27,7 +27,7 @@ import it.vfsfitvnm.innertube.models.NavigationEndpoint import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.LocalPlayerAwareWindowInsets import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder -import it.vfsfitvnm.vimusic.models.DetailedSong +import it.vfsfitvnm.vimusic.models.Song import it.vfsfitvnm.vimusic.ui.components.LocalMenuState import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop import it.vfsfitvnm.vimusic.ui.components.themed.Header @@ -54,7 +54,7 @@ fun LocalSongSearch( val binder = LocalPlayerServiceBinder.current val menuState = LocalMenuState.current - var items by persistList("search/local/songs") + var items by persistList("search/local/songs") LaunchedEffect(textFieldValue.text) { if (textFieldValue.text.length > 1) { @@ -105,7 +105,7 @@ fun LocalSongSearch( items( items = items, - key = DetailedSong::id, + key = Song::id, ) { song -> SongItem( song = song, diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Utils.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Utils.kt index 01c41dc..d6562d1 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Utils.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/Utils.kt @@ -7,11 +7,11 @@ import androidx.core.net.toUri import androidx.core.os.bundleOf import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata -import it.vfsfitvnm.vimusic.models.DetailedSong import it.vfsfitvnm.innertube.Innertube import it.vfsfitvnm.innertube.models.bodies.ContinuationBody import it.vfsfitvnm.innertube.requests.playlistPage import it.vfsfitvnm.innertube.utils.plus +import it.vfsfitvnm.vimusic.models.Song val Innertube.SongItem.asMediaItem: MediaItem get() = MediaItem.Builder() @@ -26,7 +26,6 @@ val Innertube.SongItem.asMediaItem: MediaItem .setArtworkUri(thumbnail?.url?.toUri()) .setExtras( bundleOf( - "videoId" to key, "albumId" to album?.endpoint?.browseId, "durationText" to durationText, "artistNames" to authors?.filter { it.endpoint != null }?.mapNotNull { it.name }, @@ -49,7 +48,6 @@ val Innertube.VideoItem.asMediaItem: MediaItem .setArtworkUri(thumbnail?.url?.toUri()) .setExtras( bundleOf( - "videoId" to key, "durationText" to durationText, "artistNames" to if (isOfficialMusicVideo) authors?.filter { it.endpoint != null }?.mapNotNull { it.name } else null, "artistIds" to if (isOfficialMusicVideo) authors?.mapNotNull { it.endpoint?.browseId } else null, @@ -59,7 +57,7 @@ val Innertube.VideoItem.asMediaItem: MediaItem ) .build() -val DetailedSong.asMediaItem: MediaItem +val Song.asMediaItem: MediaItem get() = MediaItem.Builder() .setMediaMetadata( MediaMetadata.Builder() @@ -68,10 +66,6 @@ val DetailedSong.asMediaItem: MediaItem .setArtworkUri(thumbnailUrl?.toUri()) .setExtras( bundleOf( - "videoId" to id, - "albumId" to albumId, - "artistNames" to artists?.map { it.name }, - "artistIds" to artists?.map { it.id }, "durationText" to durationText ) )