From 837ab9ac5df2164e3b6aaeac0afb4c40b51212f5 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Mon, 28 Sep 2026 06:09:02 +0000 Subject: [PATCH] trim the play entries, calm the badge, and only watch the visible tab The album and playlist menus lose Play and Shuffle and the song menu loses Play; tapping the row or the detail hero buttons already covers those, so the ItemActions play/shuffle callbacks and the artist view-model methods behind them go too. The badge was a filled circle that stood out over the art. It is now a white glyph with a soft shadow in the icon's own shape, with no background behind it. The library screen held all three grouped status flows at once, so the two tabs off screen ran queries for badges nobody could see. It now collects only the visible tab's map. --- .../ui/components/DownloadIndicator.kt | 66 +++++++++++-------- .../ui/components/ItemContextMenu.kt | 14 ---- .../subtracks/ui/library/AlbumDetailScreen.kt | 3 - .../ui/library/ArtistDetailScreen.kt | 2 - .../ui/library/ArtistDetailViewModel.kt | 12 ---- .../com/subtracks/ui/library/LibraryScreen.kt | 14 ++-- .../subtracks/ui/library/LibraryViewModel.kt | 37 +++-------- .../ui/library/PlaylistDetailScreen.kt | 2 - 8 files changed, 52 insertions(+), 98 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/ui/components/DownloadIndicator.kt b/app/src/main/kotlin/com/subtracks/ui/components/DownloadIndicator.kt index 0ff225b0..4db5a484 100644 --- a/app/src/main/kotlin/com/subtracks/ui/components/DownloadIndicator.kt +++ b/app/src/main/kotlin/com/subtracks/ui/components/DownloadIndicator.kt @@ -1,17 +1,17 @@ package com.subtracks.ui.components import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.DownloadDone import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.blur +import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp @@ -38,34 +38,44 @@ fun ListDownloadIndicator( val current = status ?: return val kind = current.indicator() if (kind == DownloadIndicator.Hidden) return - Surface( - shape = CircleShape, - color = MaterialTheme.colorScheme.surface.copy(alpha = 0.85f), - modifier = modifier.size(24.dp), - ) { - Box(contentAlignment = Alignment.Center) { - when (kind) { - DownloadIndicator.Complete -> { - Icon( - imageVector = Icons.Rounded.DownloadDone, - contentDescription = DOWNLOADED_INDICATOR_TAG, - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(16.dp), - ) - } + Box(modifier = modifier.size(20.dp), contentAlignment = Alignment.Center) { + when (kind) { + DownloadIndicator.Complete -> { + Icon( + imageVector = Icons.Rounded.DownloadDone, + contentDescription = null, + tint = ICON_SHADOW, + modifier = Modifier.size(18.dp).blur(3.dp).offset(y = 1.dp), + ) + Icon( + imageVector = Icons.Rounded.DownloadDone, + contentDescription = DOWNLOADED_INDICATOR_TAG, + tint = Color.White, + modifier = Modifier.size(18.dp), + ) + } - DownloadIndicator.InProgress -> { - CircularProgressIndicator( - progress = { current.downloaded.toFloat() / current.total }, - strokeWidth = 2.dp, - modifier = Modifier.size(14.dp).semantics { contentDescription = DOWNLOADING_INDICATOR_TAG }, - ) - } + DownloadIndicator.InProgress -> { + val progress = { current.downloaded.toFloat() / current.total } + CircularProgressIndicator( + progress = progress, + color = ICON_SHADOW, + strokeWidth = 2.dp, + modifier = Modifier.size(16.dp).blur(3.dp).offset(y = 1.dp), + ) + CircularProgressIndicator( + progress = progress, + color = Color.White, + strokeWidth = 2.dp, + modifier = Modifier.size(16.dp).semantics { contentDescription = DOWNLOADING_INDICATOR_TAG }, + ) + } - DownloadIndicator.Hidden -> { - Unit - } + DownloadIndicator.Hidden -> { + Unit } } } } + +private val ICON_SHADOW = Color.Black.copy(alpha = 0.45f) diff --git a/app/src/main/kotlin/com/subtracks/ui/components/ItemContextMenu.kt b/app/src/main/kotlin/com/subtracks/ui/components/ItemContextMenu.kt index df04357d..06328b80 100644 --- a/app/src/main/kotlin/com/subtracks/ui/components/ItemContextMenu.kt +++ b/app/src/main/kotlin/com/subtracks/ui/components/ItemContextMenu.kt @@ -18,8 +18,6 @@ import androidx.compose.material.icons.rounded.Cancel import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.Download import androidx.compose.material.icons.rounded.Person -import androidx.compose.material.icons.rounded.PlayArrow -import androidx.compose.material.icons.rounded.Shuffle import androidx.compose.material.icons.rounded.SkipNext import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.StarBorder @@ -137,11 +135,6 @@ fun MenuTarget.listDownload(): ListDownloadStatus? = } class ItemActions( - val playSong: ((SongModel) -> Unit)? = null, - val playAlbum: (AlbumModel) -> Unit = {}, - val shuffleAlbum: (AlbumModel) -> Unit = {}, - val playPlaylist: (PlaylistModel) -> Unit = {}, - val shufflePlaylist: (PlaylistModel) -> Unit = {}, val playNext: ((QueueRef) -> Unit)? = null, val addToQueue: ((QueueRef) -> Unit)? = null, val download: ((SongModel) -> Unit)? = null, @@ -213,9 +206,6 @@ fun ItemContextMenu( MenuHeader(target) when (target) { is MenuTarget.Song -> { - actions.playSong?.let { play -> - MenuItem(Icons.Rounded.PlayArrow, "Play") { dismiss { play(target.song) } } - } queueItems() DownloadItem(target.download, target.song, actions, ::dismiss) StarItem( @@ -240,8 +230,6 @@ fun ItemContextMenu( } is MenuTarget.Album -> { - MenuItem(Icons.Rounded.PlayArrow, "Play") { dismiss { actions.playAlbum(target.album) } } - MenuItem(Icons.Rounded.Shuffle, "Shuffle") { dismiss { actions.shuffleAlbum(target.album) } } queueItems() BulkDownloadItem(target, actions, ::dismiss) StarItem( @@ -268,8 +256,6 @@ fun ItemContextMenu( } is MenuTarget.Playlist -> { - MenuItem(Icons.Rounded.PlayArrow, "Play") { dismiss { actions.playPlaylist(target.playlist) } } - MenuItem(Icons.Rounded.Shuffle, "Shuffle") { dismiss { actions.shufflePlaylist(target.playlist) } } queueItems() BulkDownloadItem(target, actions, ::dismiss) } diff --git a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt index 7ea89ab5..b7bd4eb0 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt @@ -79,9 +79,6 @@ fun AlbumDetailRoute( } val actions = ItemActions( - playSong = { song -> songs.indexOfFirst { it.id == song.id }.takeIf { it >= 0 }?.let(viewModel::play) }, - playAlbum = { viewModel.playAll() }, - shuffleAlbum = { viewModel.shuffle() }, playNext = { playbackController.playNext(it.sourceId, it.kind, it.refId) }, addToQueue = { playbackController.addToQueue(it.sourceId, it.kind, it.refId) }, download = viewModel::download, diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt index b0d849a6..7e652796 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt @@ -154,8 +154,6 @@ fun ArtistDetailRoute( val shortcutArt = remember(coverArtId) { coverArtId?.let { viewModel.coverArt(it, true) } } val actions = ItemActions( - playAlbum = { viewModel.playAlbum(it.id) }, - shuffleAlbum = { viewModel.shuffleAlbum(it.id) }, playNext = { playbackController.playNext(it.sourceId, it.kind, it.refId) }, addToQueue = { playbackController.addToQueue(it.sourceId, it.kind, it.refId) }, bulkDownload = { target, action -> diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt index a400347c..ffcca189 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt @@ -99,16 +99,4 @@ class ArtistDetailViewModel( coverArt: String?, thumbnail: Boolean, ): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail) - - fun playAlbum(albumId: String) { - viewModelScope.launch { - playbackController.playAlbum(sourceId.first(), albumId, 0) - } - } - - fun shuffleAlbum(albumId: String) { - viewModelScope.launch { - playbackController.shuffleAlbum(sourceId.first(), albumId) - } - } } diff --git a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt index 23816ce7..17a9d9ed 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt @@ -216,17 +216,11 @@ fun LibraryRoute( } val playback by playbackController.state.collectAsStateWithLifecycle() val artwork = rememberArtworkColors(playbackController.coverArt(playback.item, thumbnail = true)) - val albumDownloads by viewModel.albumDownloads.collectAsStateWithLifecycle() - val artistDownloads by viewModel.artistDownloads.collectAsStateWithLifecycle() - val playlistDownloads by viewModel.playlistDownloads.collectAsStateWithLifecycle() + val downloads by viewModel.downloads(listTab).collectAsStateWithLifecycle() val scope = rememberCoroutineScope() var pendingDelete by remember { mutableStateOf(null) } val itemActions = ItemActions( - playAlbum = { viewModel.playAlbum(it.id) }, - shuffleAlbum = { viewModel.shuffleAlbum(it.id) }, - playPlaylist = { viewModel.playPlaylist(it.id) }, - shufflePlaylist = { viewModel.shufflePlaylist(it.id) }, playNext = { playbackController.playNext(it.sourceId, it.kind, it.refId) }, addToQueue = { playbackController.addToQueue(it.sourceId, it.kind, it.refId) }, bulkDownload = { target, action -> @@ -282,9 +276,9 @@ fun LibraryRoute( }, search = search, onSearchChange = { viewModel.setSearch(listTab, it) }, - albumDownloads = albumDownloads, - artistDownloads = artistDownloads, - playlistDownloads = playlistDownloads, + albumDownloads = downloads.takeIf { listTab == LibraryListTab.Albums }.orEmpty(), + artistDownloads = downloads.takeIf { listTab == LibraryListTab.Artists }.orEmpty(), + playlistDownloads = downloads.takeIf { listTab == LibraryListTab.Playlists }.orEmpty(), ) pendingDelete?.let { pending -> diff --git a/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt index 36fa1451..1c2654cb 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt @@ -95,21 +95,28 @@ class LibraryViewModel( private val activeSourceId = libraryRepository.activeSourceId.filterNotNull() - val albumDownloads: StateFlow> = + private val albumDownloads: StateFlow> = activeSourceId .flatMapLatest { downloadRepository.statuses(it, DownloadList.Album) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyMap()) - val artistDownloads: StateFlow> = + private val artistDownloads: StateFlow> = activeSourceId .flatMapLatest { downloadRepository.statuses(it, DownloadList.Artist) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyMap()) - val playlistDownloads: StateFlow> = + private val playlistDownloads: StateFlow> = activeSourceId .flatMapLatest { downloadRepository.statuses(it, DownloadList.Playlist) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyMap()) + fun downloads(tab: LibraryListTab): StateFlow> = + when (tab) { + LibraryListTab.Albums -> albumDownloads + LibraryListTab.Artists -> artistDownloads + LibraryListTab.Playlists -> playlistDownloads + } + suspend fun downloadedBytes( list: DownloadList, refId: String, @@ -149,30 +156,6 @@ class LibraryViewModel( thumbnail: Boolean, ): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail) - fun playAlbum(albumId: String) { - viewModelScope.launch { - sourceRepository.activeSourceIdOnce()?.let { playbackController.playAlbum(it, albumId, 0) } - } - } - - fun shuffleAlbum(albumId: String) { - viewModelScope.launch { - sourceRepository.activeSourceIdOnce()?.let { playbackController.shuffleAlbum(it, albumId) } - } - } - - fun playPlaylist(playlistId: String) { - viewModelScope.launch { - sourceRepository.activeSourceIdOnce()?.let { playbackController.playPlaylist(it, playlistId, 0) } - } - } - - fun shufflePlaylist(playlistId: String) { - viewModelScope.launch { - sourceRepository.activeSourceIdOnce()?.let { playbackController.shufflePlaylist(it, playlistId) } - } - } - fun sync() = syncManager.requestSync() } diff --git a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt index 6d730123..3701a055 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt @@ -64,8 +64,6 @@ fun PlaylistDetailRoute( } val actions = ItemActions( - playPlaylist = { viewModel.playAll() }, - shufflePlaylist = { viewModel.shuffle() }, playNext = { playbackController.playNext(it.sourceId, it.kind, it.refId) }, addToQueue = { playbackController.addToQueue(it.sourceId, it.kind, it.refId) }, download = viewModel::download,