mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 23:21:59 +01:00
play the songs list from the songs tab
This commit is contained in:
@@ -21,6 +21,13 @@ internal const val PLAYLIST_SONGS_SQL =
|
||||
"WHERE playlist_songs.sourceId = :sourceId AND playlist_songs.playlistId = :playlistId " +
|
||||
"ORDER BY playlist_songs.position"
|
||||
|
||||
internal const val SONGS_SQL =
|
||||
"SELECT songs.*, albums.coverArt AS coverArt FROM songs " +
|
||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||
"WHERE songs.sourceId = :sourceId " +
|
||||
"ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, " +
|
||||
"songs.title COLLATE NOCASE, songs.id"
|
||||
|
||||
@Dao
|
||||
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
|
||||
interface LibraryDao {
|
||||
@@ -93,12 +100,7 @@ interface LibraryDao {
|
||||
@Query("SELECT * FROM albums WHERE sourceId = :sourceId ORDER BY created DESC, name COLLATE NOCASE")
|
||||
fun albumsByRecentlyAdded(sourceId: Long): PagingSource<Int, Album>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt FROM songs " +
|
||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||
"WHERE songs.sourceId = :sourceId " +
|
||||
"ORDER BY songs.title COLLATE NOCASE, songs.artist COLLATE NOCASE",
|
||||
)
|
||||
@Query(SONGS_SQL)
|
||||
fun songs(sourceId: Long): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query("SELECT * FROM playlists WHERE sourceId = :sourceId ORDER BY name COLLATE NOCASE")
|
||||
|
||||
@@ -47,6 +47,9 @@ interface QueueDao {
|
||||
albumId: String,
|
||||
): Long
|
||||
|
||||
@Query("SELECT COUNT(*) FROM songs WHERE sourceId = :sourceId")
|
||||
suspend fun songsLength(sourceId: Long): Long
|
||||
|
||||
@Query("SELECT COUNT(*) FROM songs WHERE sourceId = :sourceId AND id = :songId")
|
||||
suspend fun songLength(
|
||||
sourceId: Long,
|
||||
@@ -67,6 +70,12 @@ interface QueueDao {
|
||||
offset: Long,
|
||||
): SongListItem?
|
||||
|
||||
@Query("$SONGS_SQL LIMIT 1 OFFSET :offset")
|
||||
suspend fun songAt(
|
||||
sourceId: Long,
|
||||
offset: Long,
|
||||
): SongListItem?
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt FROM songs " +
|
||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||
|
||||
@@ -175,7 +175,7 @@ data class SearchIndex(
|
||||
val title: String,
|
||||
)
|
||||
|
||||
enum class QueueKind { Playlist, Album, Song }
|
||||
enum class QueueKind { Playlist, Album, Song, Songs }
|
||||
|
||||
@Entity(
|
||||
tableName = "queue_entries",
|
||||
|
||||
@@ -53,6 +53,8 @@ class QueueRepository(
|
||||
songId: String,
|
||||
) = QueueEntry(position = 0, sourceId = sourceId, kind = QueueKind.Song, refId = songId)
|
||||
|
||||
fun songsEntry(sourceId: Long) = QueueEntry(position = 0, sourceId = sourceId, kind = QueueKind.Songs, refId = "")
|
||||
|
||||
suspend fun replace(entries: List<QueueEntry>) {
|
||||
db.useWriterConnection { transactor ->
|
||||
transactor.immediateTransaction {
|
||||
@@ -99,6 +101,7 @@ class QueueRepository(
|
||||
QueueKind.Playlist -> dao.playlistSongAt(entry.sourceId, entry.refId, index)
|
||||
QueueKind.Album -> dao.albumSongAt(entry.sourceId, entry.refId, index)
|
||||
QueueKind.Song -> if (offset == 0L) dao.song(entry.sourceId, entry.refId) else null
|
||||
QueueKind.Songs -> dao.songAt(entry.sourceId, index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +111,7 @@ class QueueRepository(
|
||||
QueueKind.Song -> dao.songLength(sourceId, refId)
|
||||
QueueKind.Playlist -> dao.playlistLength(sourceId, refId)
|
||||
QueueKind.Album -> dao.albumLength(sourceId, refId)
|
||||
QueueKind.Songs -> dao.songsLength(sourceId)
|
||||
}
|
||||
val available = (total - offset).coerceAtLeast(0)
|
||||
return count?.coerceAtMost(available) ?: available
|
||||
|
||||
@@ -34,7 +34,7 @@ fun appModule(
|
||||
single<PlayerConnection> { MediaSessionConnection(context.applicationContext, get()) }
|
||||
single { PlaybackController(get(), get(), get()) }
|
||||
viewModel { RootViewModel(get()) }
|
||||
viewModel { LibraryViewModel(get(), get(), get(), get()) }
|
||||
viewModel { LibraryViewModel(get(), get(), get(), get(), get()) }
|
||||
viewModel { SettingsViewModel(get(), get()) }
|
||||
viewModel { AddSourceViewModel(get(), get()) }
|
||||
viewModel { params -> AlbumDetailViewModel(get(), get(), get(), params.get()) }
|
||||
|
||||
@@ -118,6 +118,11 @@ class PlaybackController(
|
||||
startPosition: Long,
|
||||
) = play(listOf(queueRepository.playlistEntry(sourceId, playlistId)), startPosition)
|
||||
|
||||
fun playSongs(
|
||||
sourceId: Long,
|
||||
startPosition: Long,
|
||||
) = play(listOf(queueRepository.songsEntry(sourceId)), startPosition)
|
||||
|
||||
fun togglePlayPause() {
|
||||
val player = player ?: return
|
||||
if (player.isEnded) {
|
||||
|
||||
@@ -93,6 +93,7 @@ fun LibraryRoute(
|
||||
) {
|
||||
var selectedTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) }
|
||||
val syncing by viewModel.syncing.collectAsStateWithLifecycle()
|
||||
val playingSongId by viewModel.playingSongId.collectAsStateWithLifecycle()
|
||||
LibraryScreen(
|
||||
selectedTab = selectedTab,
|
||||
onTabSelected = { selectedTab = it },
|
||||
@@ -105,8 +106,10 @@ fun LibraryRoute(
|
||||
coverArt = viewModel::coverArt,
|
||||
onAlbumClick = onAlbumClick,
|
||||
onPlaylistClick = onPlaylistClick,
|
||||
onSongClick = viewModel::playSong,
|
||||
onSync = viewModel::sync,
|
||||
onOpenSettings = onOpenSettings,
|
||||
playingSongId = playingSongId,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -122,9 +125,11 @@ fun LibraryScreen(
|
||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||
onAlbumClick: (Album) -> Unit,
|
||||
onPlaylistClick: (Playlist) -> Unit,
|
||||
onSongClick: (Int) -> Unit,
|
||||
onSync: () -> Unit,
|
||||
onOpenSettings: () -> Unit,
|
||||
syncing: Boolean = false,
|
||||
playingSongId: String? = null,
|
||||
bottomInset: Dp = 0.dp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -206,7 +211,7 @@ fun LibraryScreen(
|
||||
when (LibraryTab.entries[page]) {
|
||||
LibraryTab.Albums -> AlbumsContent(albums, coverArt, bottomInset, onAlbumClick)
|
||||
LibraryTab.Artists -> ArtistsContent(artists, coverArt, bottomInset)
|
||||
LibraryTab.Songs -> SongsContent(songs, coverArt, bottomInset)
|
||||
LibraryTab.Songs -> SongsContent(songs, coverArt, bottomInset, onSongClick, playingSongId)
|
||||
LibraryTab.Playlists -> PlaylistsContent(playlists, coverArt, bottomInset, onPlaylistClick)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,14 @@ import com.subtracks.data.model.Album
|
||||
import com.subtracks.data.model.Artist
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
import com.subtracks.data.model.Playlist
|
||||
import com.subtracks.data.model.QueueKind
|
||||
import com.subtracks.data.model.SongListItem
|
||||
import com.subtracks.data.prefs.UserPreferences
|
||||
import com.subtracks.data.repo.LibraryRepository
|
||||
import com.subtracks.data.repo.SourceRepository
|
||||
import com.subtracks.data.sync.SyncManager
|
||||
import com.subtracks.data.sync.SyncStatus
|
||||
import com.subtracks.playback.PlaybackController
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -22,12 +24,14 @@ import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class LibraryViewModel(
|
||||
private val libraryRepository: LibraryRepository,
|
||||
private val sourceRepository: SourceRepository,
|
||||
private val syncManager: SyncManager,
|
||||
private val playbackController: PlaybackController,
|
||||
userPreferences: UserPreferences,
|
||||
) : ViewModel() {
|
||||
val albums: Flow<PagingData<Album>> =
|
||||
@@ -60,10 +64,22 @@ class LibraryViewModel(
|
||||
.map { it == SyncStatus.Running }
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
|
||||
|
||||
val playingSongId: StateFlow<String?> =
|
||||
playbackController.state
|
||||
.map { state -> state.item?.id?.takeIf { state.context?.kind == QueueKind.Songs } }
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
||||
|
||||
fun coverArt(
|
||||
coverArt: String?,
|
||||
thumbnail: Boolean,
|
||||
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
||||
|
||||
fun playSong(position: Int) {
|
||||
viewModelScope.launch {
|
||||
val sourceId = sourceRepository.activeSourceIdOnce() ?: return@launch
|
||||
playbackController.playSongs(sourceId, position.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
fun sync() = syncManager.requestSync()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.subtracks.ui.library
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -36,6 +37,8 @@ fun SongsContent(
|
||||
items: LazyPagingItems<SongListItem>,
|
||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||
bottomInset: Dp,
|
||||
onSongClick: (Int) -> Unit,
|
||||
playingSongId: String? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when {
|
||||
@@ -55,7 +58,13 @@ fun SongsContent(
|
||||
items(count = items.itemCount, key = items.itemKey { it.song.id }) { index ->
|
||||
val item = items[index]
|
||||
if (item != null) {
|
||||
SongRow(song = item.song, coverArtId = item.coverArt, coverArt = coverArt)
|
||||
SongRow(
|
||||
song = item.song,
|
||||
coverArtId = item.coverArt,
|
||||
coverArt = coverArt,
|
||||
isPlaying = item.song.id == playingSongId,
|
||||
modifier = Modifier.clickable { onSongClick(index) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user