diff --git a/app/src/main/kotlin/com/subtracks/data/db/DownloadDao.kt b/app/src/main/kotlin/com/subtracks/data/db/DownloadDao.kt index f614ebb0..df70478b 100644 --- a/app/src/main/kotlin/com/subtracks/data/db/DownloadDao.kt +++ b/app/src/main/kotlin/com/subtracks/data/db/DownloadDao.kt @@ -107,7 +107,7 @@ interface DownloadDao { @Query( "SELECT d.sourceId AS sourceId, d.songId AS songId, s.title AS title, s.albumId AS albumId, al.name AS albumName, " + "al.artistId AS artistId, ar.name AS artistName, d.status AS status, " + - "d.bytes AS bytes, d.total AS total, d.total AS size " + + "d.bytes AS bytes, d.total AS total " + "FROM song_downloads d " + "JOIN songs s ON s.sourceId = d.sourceId AND s.id = d.songId " + "LEFT JOIN albums al ON al.sourceId = s.sourceId AND al.id = s.albumId " + diff --git a/app/src/main/kotlin/com/subtracks/ui/downloads/DownloadsScreen.kt b/app/src/main/kotlin/com/subtracks/ui/downloads/DownloadsScreen.kt index c6b81bed..d636805b 100644 --- a/app/src/main/kotlin/com/subtracks/ui/downloads/DownloadsScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/downloads/DownloadsScreen.kt @@ -262,11 +262,11 @@ private fun NodeRow( trailingContent = { if (progress != null) { IconButton(onClick = onCancel) { - Icon(Icons.Rounded.Close, contentDescription = "Cancel download") + Icon(Icons.Rounded.Close, contentDescription = "Cancel download of $name") } } else { IconButton(onClick = onDelete) { - Icon(Icons.Rounded.Delete, contentDescription = "Delete downloads") + Icon(Icons.Rounded.Delete, contentDescription = "Delete downloads of $name") } } }, @@ -280,7 +280,7 @@ private fun activeDownloads(songs: List): List = internal fun nodeProgress(songs: List): Float? { val active = activeDownloads(songs) - if (active.isEmpty() || songs.isEmpty()) return null + if (active.isEmpty()) return null val completed = songs.count { it.status == DownloadStatus.Completed } val partial = active.sumOf { (it.progress ?: 0f).toDouble() } return ((completed + partial) / songs.size).toFloat().coerceIn(0f, 1f) @@ -298,27 +298,30 @@ private fun SongRow( encoding = if (song.status == DownloadStatus.Completed) loadEncoding(song) else null } val active = song.status == DownloadStatus.Queued || song.status == DownloadStatus.Running + val barModifier = Modifier.fillMaxWidth().padding(top = 4.dp) ListItem( headlineContent = { Text(song.title, maxLines = 1, overflow = TextOverflow.Ellipsis) }, supportingContent = { Column { Text(songSubtitle(song, encoding), maxLines = 1, overflow = TextOverflow.Ellipsis) if (active) { - LinearProgressIndicator( - progress = { song.progress ?: 0f }, - modifier = Modifier.fillMaxWidth().padding(top = 4.dp), - ) + val progress = song.progress + if (progress == null) { + LinearProgressIndicator(modifier = barModifier) + } else { + LinearProgressIndicator(progress = { progress }, modifier = barModifier) + } } } }, trailingContent = { if (active) { IconButton(onClick = onCancel) { - Icon(Icons.Rounded.Close, contentDescription = "Cancel download") + Icon(Icons.Rounded.Close, contentDescription = "Cancel download of ${song.title}") } } else { IconButton(onClick = onDelete) { - Icon(Icons.Rounded.Delete, contentDescription = "Delete download") + Icon(Icons.Rounded.Delete, contentDescription = "Delete download of ${song.title}") } } }, diff --git a/app/src/main/kotlin/com/subtracks/ui/downloads/DownloadsViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/downloads/DownloadsViewModel.kt index de986d91..9711dbd2 100644 --- a/app/src/main/kotlin/com/subtracks/ui/downloads/DownloadsViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/downloads/DownloadsViewModel.kt @@ -82,7 +82,6 @@ class DownloadsViewModel( fun cancelSongs(songIds: List) { viewModelScope.launch { downloadRepository.cancel(sourceId.first(), songIds) - playbackController.refreshMediaItems() } } diff --git a/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadTreeTest.kt b/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadTreeTest.kt index 4bcf6ec6..2481288d 100644 --- a/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadTreeTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadTreeTest.kt @@ -58,6 +58,20 @@ class DownloadTreeTest { assertNull(nodeProgress(listOf(song("s3", "Three", "ar1", "Artist", "al1", "Album", size = 1)))) } + @Test + fun nodeProgressCountsAFailedTrackInTheDenominator() { + val songs = + listOf( + song("s1", "One", "ar1", "Artist", "al1", "Album", size = 100), + song("s2", "Two", "ar1", "Artist", "al1", "Album", size = 100) + .copy(status = DownloadStatus.Failed), + song("s3", "Three", "ar1", "Artist", "al1", "Album", size = 100) + .copy(status = DownloadStatus.Running, bytes = 50, total = 100), + ) + + assertEquals(0.5f, nodeProgress(songs)!!, 0.0001f) + } + private fun song( id: String, title: String, diff --git a/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsScreenTest.kt b/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsScreenTest.kt index 924fd863..fd1851d5 100644 --- a/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsScreenTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsScreenTest.kt @@ -53,10 +53,10 @@ class DownloadsScreenTest { onCancel = { cancelled = it }, ) - composeRule.onAllNodesWithContentDescription("Cancel download").assertCountEquals(1) - composeRule.onAllNodesWithContentDescription("Delete downloads").assertCountEquals(1) + composeRule.onAllNodesWithContentDescription("Cancel download", substring = true).assertCountEquals(1) + composeRule.onAllNodesWithContentDescription("Delete downloads", substring = true).assertCountEquals(1) - composeRule.onNodeWithContentDescription("Cancel download").performClick() + composeRule.onNodeWithContentDescription("Cancel download of Active").performClick() assertEquals(listOf("s1"), cancelled) } @@ -66,8 +66,8 @@ class DownloadsScreenTest { expandArtistAndAlbum() - composeRule.onAllNodesWithContentDescription("Cancel download").assertCountEquals(3) - composeRule.onAllNodesWithContentDescription("Delete download").assertCountEquals(1) + composeRule.onAllNodesWithContentDescription("Cancel download", substring = true).assertCountEquals(3) + composeRule.onAllNodesWithContentDescription("Delete download of Done").assertCountEquals(1) } private fun artist( diff --git a/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt b/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt index b05847e9..fb31ee45 100644 --- a/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/downloads/DownloadsViewModelTest.kt @@ -150,6 +150,17 @@ class DownloadsViewModelTest { assertNotNull("a completed download should survive a cancel", row("s2")) } + @Test + fun cancelSongsRemovesAQueuedDownloadWithoutAnEngineId() { + seedDownloads() + runBlocking { db.downloadDao().upsert(SongDownload(sourceId, "s1", DownloadStatus.Queued, engineId = null)) } + + viewModel.cancelSongs(listOf("s1")) + await { row("s1") == null } + + assertNull(row("s1")) + } + private fun seedDownloads() { sourceId = runBlocking {