confirm the artist delete too

Only a single song deletes without asking; the artist's delete now opens the same
dialog as the album and playlist, naming the artist and the space it frees. The
artist's download action stays in its more-menu rather than getting a visible
control, deliberately, so bulk downloading a whole artist is a considered
action.
This commit is contained in:
austinried
2026-10-04 00:30:03 +00:00
parent 475793059b
commit 37ccfd0b17
3 changed files with 19 additions and 1 deletions
@@ -50,7 +50,9 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -80,6 +82,7 @@ import com.subtracks.data.source.StarType
import com.subtracks.playback.PlaybackController import com.subtracks.playback.PlaybackController
import com.subtracks.ui.components.ContextMenuHost import com.subtracks.ui.components.ContextMenuHost
import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.CoverArt
import com.subtracks.ui.components.DeleteDownloadsDialog
import com.subtracks.ui.components.ItemActions import com.subtracks.ui.components.ItemActions
import com.subtracks.ui.components.MenuTarget import com.subtracks.ui.components.MenuTarget
import com.subtracks.ui.components.rememberViewportFill import com.subtracks.ui.components.rememberViewportFill
@@ -91,6 +94,7 @@ import com.subtracks.ui.theme.baseArtworkColors
import com.subtracks.ui.theme.heroBarColor import com.subtracks.ui.theme.heroBarColor
import com.subtracks.ui.theme.rememberArtworkColors import com.subtracks.ui.theme.rememberArtworkColors
import com.subtracks.ui.theme.rememberOverlaidNameBusy import com.subtracks.ui.theme.rememberOverlaidNameBusy
import kotlinx.coroutines.launch
import org.koin.compose.koinInject import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
@@ -140,6 +144,8 @@ fun ArtistDetailRoute(
val art by viewModel.art.collectAsStateWithLifecycle() val art by viewModel.art.collectAsStateWithLifecycle()
val artThumbnail by viewModel.artThumbnail.collectAsStateWithLifecycle() val artThumbnail by viewModel.artThumbnail.collectAsStateWithLifecycle()
val downloadStatus by viewModel.downloadStatus.collectAsStateWithLifecycle() val downloadStatus by viewModel.downloadStatus.collectAsStateWithLifecycle()
val scope = rememberCoroutineScope()
var pendingDelete by remember { mutableStateOf<Long?>(null) }
val shortcutArt = remember(coverArtId) { coverArtId?.let { viewModel.coverArt(it, true) } } val shortcutArt = remember(coverArtId) { coverArtId?.let { viewModel.coverArt(it, true) } }
val actions = val actions =
ItemActions( ItemActions(
@@ -149,7 +155,7 @@ fun ArtistDetailRoute(
addToQueue = { playbackController.addToQueue(it.sourceId, it.kind, it.refId) }, addToQueue = { playbackController.addToQueue(it.sourceId, it.kind, it.refId) },
downloadArtist = { viewModel.onDownloadAction(BulkDownloadAction.Download) }, downloadArtist = { viewModel.onDownloadAction(BulkDownloadAction.Download) },
cancelArtistDownload = { viewModel.onDownloadAction(BulkDownloadAction.Cancel) }, cancelArtistDownload = { viewModel.onDownloadAction(BulkDownloadAction.Cancel) },
deleteArtistDownload = { viewModel.onDownloadAction(BulkDownloadAction.Delete) }, deleteArtistDownload = { scope.launch { pendingDelete = viewModel.downloadedBytes() } },
setStar = setStar, setStar = setStar,
viewAlbum = onViewAlbum, viewAlbum = onViewAlbum,
) )
@@ -167,6 +173,15 @@ fun ArtistDetailRoute(
starred = artist?.starred != null, starred = artist?.starred != null,
onToggleStar = artist?.let { a -> { setStar(StarType.Artist, a.id, a.starred == null) } }, onToggleStar = artist?.let { a -> { setStar(StarType.Artist, a.id, a.starred == null) } },
) )
pendingDelete?.let { bytes ->
DeleteDownloadsDialog(
name = artist?.name.orEmpty(),
bytes = bytes,
onConfirm = { viewModel.onDownloadAction(BulkDownloadAction.Delete) },
onDismiss = { pendingDelete = null },
)
}
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -44,6 +44,8 @@ class ArtistDetailViewModel(
.flatMapLatest { downloadRepository.status(it, DownloadList.Artist, artistId) } .flatMapLatest { downloadRepository.status(it, DownloadList.Artist, artistId) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), ListDownloadStatus()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), ListDownloadStatus())
suspend fun downloadedBytes(): Long = downloadRepository.downloadedBytes(sourceId.first(), DownloadList.Artist, artistId)
fun onDownloadAction(action: BulkDownloadAction) { fun onDownloadAction(action: BulkDownloadAction) {
viewModelScope.launch { viewModelScope.launch {
downloadRepository.applyAction(sourceId.first(), DownloadList.Artist, artistId, action) downloadRepository.applyAction(sourceId.first(), DownloadList.Artist, artistId, action)
@@ -667,6 +667,7 @@ class DownloadRepositoryTest {
writeFile(1, "s3", "123") writeFile(1, "s3", "123")
assertEquals(5L, repository.downloadedBytes(1, DownloadList.Album, "al1")) assertEquals(5L, repository.downloadedBytes(1, DownloadList.Album, "al1"))
assertEquals(5L, repository.downloadedBytes(1, DownloadList.Artist, "ar1"))
} }
private fun requestedSongIds(): List<String> = engine.requests.map { it.second.path.substringAfterLast('/') } private fun requestedSongIds(): List<String> = engine.requests.map { it.second.path.substringAfterLast('/') }