library: drop the download progress indicator

This commit is contained in:
austinried
2026-10-21 06:04:23 +00:00
parent 6f1e1612e1
commit e487d2e0b1
7 changed files with 1 additions and 141 deletions
@@ -78,13 +78,6 @@ data class ListDownloadStatus(
val complete: Boolean get() = total > 0 && downloaded == total val complete: Boolean get() = total > 0 && downloaded == total
} }
data class DownloadProgress(
val bytes: Long,
val total: Long,
) {
val fraction: Float? get() = fractionOf(bytes, total)
}
data class EntityDownloadStatus( data class EntityDownloadStatus(
val id: String, val id: String,
val total: Long = 0, val total: Long = 0,
@@ -396,7 +396,6 @@ private fun MainNavigation() {
navigateDetail(Routes.PLAYLIST_DETAIL, "playlistId", playlist.id, Routes.playlist(playlist.id)) navigateDetail(Routes.PLAYLIST_DETAIL, "playlistId", playlist.id, Routes.playlist(playlist.id))
}, },
onOpenSettings = { navController.navigate(Routes.SETTINGS) }, onOpenSettings = { navController.navigate(Routes.SETTINGS) },
onOpenDownloads = { navController.navigate(Routes.DOWNLOADS) },
onOpenSearch = { navController.navigate(Routes.SEARCH) }, onOpenSearch = { navController.navigate(Routes.SEARCH) },
onViewAlbum = { albumId -> navigateDetail(Routes.ALBUM_DETAIL, "albumId", albumId, Routes.album(albumId)) }, onViewAlbum = { albumId -> navigateDetail(Routes.ALBUM_DETAIL, "albumId", albumId, Routes.album(albumId)) },
onViewArtist = { artistId -> onViewArtist = { artistId ->
@@ -34,7 +34,6 @@ import androidx.compose.material.icons.rounded.Album
import androidx.compose.material.icons.rounded.ArrowDownward import androidx.compose.material.icons.rounded.ArrowDownward
import androidx.compose.material.icons.rounded.ArrowUpward import androidx.compose.material.icons.rounded.ArrowUpward
import androidx.compose.material.icons.rounded.CloudOff import androidx.compose.material.icons.rounded.CloudOff
import androidx.compose.material.icons.rounded.Download
import androidx.compose.material.icons.rounded.DownloadDone import androidx.compose.material.icons.rounded.DownloadDone
import androidx.compose.material.icons.rounded.FilterAltOff import androidx.compose.material.icons.rounded.FilterAltOff
import androidx.compose.material.icons.rounded.Home import androidx.compose.material.icons.rounded.Home
@@ -111,7 +110,6 @@ import com.subtracks.data.model.Artist
import com.subtracks.data.model.BulkDownloadAction import com.subtracks.data.model.BulkDownloadAction
import com.subtracks.data.model.CoverArtRef import com.subtracks.data.model.CoverArtRef
import com.subtracks.data.model.DownloadList import com.subtracks.data.model.DownloadList
import com.subtracks.data.model.DownloadProgress
import com.subtracks.data.model.ListDownloadStatus import com.subtracks.data.model.ListDownloadStatus
import com.subtracks.data.model.Playlist import com.subtracks.data.model.Playlist
import com.subtracks.data.prefs.AlbumSort import com.subtracks.data.prefs.AlbumSort
@@ -230,7 +228,6 @@ fun LibraryRoute(
onArtistClick: (Artist) -> Unit, onArtistClick: (Artist) -> Unit,
onPlaylistClick: (Playlist) -> Unit, onPlaylistClick: (Playlist) -> Unit,
onOpenSettings: () -> Unit, onOpenSettings: () -> Unit,
onOpenDownloads: () -> Unit = {},
onOpenSearch: () -> Unit = {}, onOpenSearch: () -> Unit = {},
onViewAlbum: (String) -> Unit, onViewAlbum: (String) -> Unit,
onViewArtist: (String) -> Unit, onViewArtist: (String) -> Unit,
@@ -261,7 +258,6 @@ fun LibraryRoute(
val listQueryState = listTab?.let { viewModel.listQuery(it).collectAsStateWithLifecycle() } val listQueryState = listTab?.let { viewModel.listQuery(it).collectAsStateWithLifecycle() }
val listQuery = listQueryState?.value ?: ListQuery("") val listQuery = listQueryState?.value ?: ListQuery("")
val offline by viewModel.offline.collectAsStateWithLifecycle() val offline by viewModel.offline.collectAsStateWithLifecycle()
val downloadProgress by viewModel.downloadProgress.collectAsStateWithLifecycle()
val playSort by viewModel.playSortAvailability.collectAsStateWithLifecycle() val playSort by viewModel.playSortAvailability.collectAsStateWithLifecycle()
val displayQuery = if (offline) listQuery.copy(downloaded = true) else listQuery val displayQuery = if (offline) listQuery.copy(downloaded = true) else listQuery
val searchState = listTab?.let { viewModel.search(it).collectAsStateWithLifecycle() } val searchState = listTab?.let { viewModel.search(it).collectAsStateWithLifecycle() }
@@ -319,7 +315,6 @@ fun LibraryRoute(
onPlaylistClick = onPlaylistClick, onPlaylistClick = onPlaylistClick,
onSync = viewModel::sync, onSync = viewModel::sync,
onOpenSettings = onOpenSettings, onOpenSettings = onOpenSettings,
onOpenDownloads = onOpenDownloads,
onOpenSearch = onOpenSearch, onOpenSearch = onOpenSearch,
onItemLongClick = { onItemLongClick = {
focusManager.clearFocus() focusManager.clearFocus()
@@ -329,7 +324,6 @@ fun LibraryRoute(
resetKeys = resetKeys, resetKeys = resetKeys,
offline = offline, offline = offline,
onExitOffline = { viewModel.setOffline(false) }, onExitOffline = { viewModel.setOffline(false) },
downloadProgress = downloadProgress,
sortOptions = sortOptionsFor(selectedTab, playSort), sortOptions = sortOptionsFor(selectedTab, playSort),
starredSupported = listTab?.supportsStarred == true, starredSupported = listTab?.supportsStarred == true,
onSortChange = { sort -> listTab?.let { viewModel.setListQuery(it, listQuery.copy(sort = sort)) } }, onSortChange = { sort -> listTab?.let { viewModel.setListQuery(it, listQuery.copy(sort = sort)) } },
@@ -404,12 +398,10 @@ fun LibraryScreen(
onSync: () -> Unit, onSync: () -> Unit,
onOpenSettings: () -> Unit, onOpenSettings: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onOpenDownloads: () -> Unit = {},
artistArt: (Artist) -> ArtistArt = { ArtistArt() }, artistArt: (Artist) -> ArtistArt = { ArtistArt() },
onOpenSearch: () -> Unit = {}, onOpenSearch: () -> Unit = {},
offline: Boolean = false, offline: Boolean = false,
onExitOffline: () -> Unit = {}, onExitOffline: () -> Unit = {},
downloadProgress: DownloadProgress? = null,
onItemLongClick: (MenuTarget) -> Unit = {}, onItemLongClick: (MenuTarget) -> Unit = {},
syncing: Boolean = false, syncing: Boolean = false,
refreshing: Boolean = false, refreshing: Boolean = false,
@@ -602,8 +594,6 @@ fun LibraryScreen(
onOpenSettings = onOpenSettings, onOpenSettings = onOpenSettings,
offline = offline, offline = offline,
onExitOffline = onExitOffline, onExitOffline = onExitOffline,
downloadProgress = downloadProgress,
onOpenDownloads = onOpenDownloads,
artwork = artwork, artwork = artwork,
syncing = syncing, syncing = syncing,
) )
@@ -868,8 +858,6 @@ internal fun LibraryTabs(
onOpenSettings: () -> Unit, onOpenSettings: () -> Unit,
offline: Boolean = false, offline: Boolean = false,
onExitOffline: () -> Unit = {}, onExitOffline: () -> Unit = {},
downloadProgress: DownloadProgress? = null,
onOpenDownloads: () -> Unit = {},
artwork: ArtworkColors?, artwork: ArtworkColors?,
syncing: Boolean = false, syncing: Boolean = false,
) { ) {
@@ -953,14 +941,7 @@ internal fun LibraryTabs(
) )
} }
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
if (downloadProgress != null) { if (offline) {
IconButton(onClick = onOpenDownloads) {
DownloadProgressIcon(
progress = downloadProgress,
color = MaterialTheme.colorScheme.primary,
)
}
} else if (offline) {
IconButton(onClick = onExitOffline) { IconButton(onClick = onExitOffline) {
Icon( Icon(
imageVector = Icons.Rounded.CloudOff, imageVector = Icons.Rounded.CloudOff,
@@ -991,41 +972,6 @@ internal fun LibraryTabs(
} }
} }
@Composable
private fun DownloadProgressIcon(
progress: DownloadProgress,
color: Color,
) {
val description = stringResource(R.string.downloads_in_progress)
Box(
modifier = Modifier.size(32.dp).semantics { contentDescription = description },
contentAlignment = Alignment.Center,
) {
val fraction = progress.fraction
if (fraction != null) {
CircularProgressIndicator(
progress = { fraction },
color = color,
trackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
strokeWidth = 2.dp,
modifier = Modifier.fillMaxSize(),
)
} else {
CircularProgressIndicator(
color = color,
strokeWidth = 2.dp,
modifier = Modifier.fillMaxSize(),
)
}
Icon(
imageVector = Icons.Rounded.Download,
contentDescription = null,
tint = color,
modifier = Modifier.size(16.dp),
)
}
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun TabButton( private fun TabButton(
@@ -9,11 +9,8 @@ import com.subtracks.data.model.Artist
import com.subtracks.data.model.BulkDownloadAction import com.subtracks.data.model.BulkDownloadAction
import com.subtracks.data.model.CoverArtRef import com.subtracks.data.model.CoverArtRef
import com.subtracks.data.model.DownloadList import com.subtracks.data.model.DownloadList
import com.subtracks.data.model.DownloadProgress
import com.subtracks.data.model.DownloadStatus
import com.subtracks.data.model.ListDownloadStatus import com.subtracks.data.model.ListDownloadStatus
import com.subtracks.data.model.Playlist import com.subtracks.data.model.Playlist
import com.subtracks.data.model.SongDownload
import com.subtracks.data.prefs.AlbumSort import com.subtracks.data.prefs.AlbumSort
import com.subtracks.data.prefs.ArtistSort import com.subtracks.data.prefs.ArtistSort
import com.subtracks.data.prefs.LibraryListTab import com.subtracks.data.prefs.LibraryListTab
@@ -118,13 +115,6 @@ class LibraryViewModel(
val offline: StateFlow<Boolean> = sourceRepository.offline val offline: StateFlow<Boolean> = sourceRepository.offline
val downloadProgress: StateFlow<DownloadProgress?> =
combine(
downloadRepository.activeDownloads(),
sourceRepository.activeSourceId(),
) { rows, sourceId -> downloadProgress(rows, sourceId) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
fun setOffline(enabled: Boolean) = sourceRepository.setOfflineMode(enabled) fun setOffline(enabled: Boolean) = sourceRepository.setOfflineMode(enabled)
val syncing: StateFlow<Boolean> = val syncing: StateFlow<Boolean> =
@@ -233,21 +223,6 @@ class LibraryViewModel(
} }
} }
internal fun downloadProgress(
rows: List<SongDownload>,
sourceId: Long?,
): DownloadProgress? {
if (sourceId == null) return null
val active = rows.filter { it.sourceId == sourceId }
if (active.isEmpty()) return null
val running = active.filter { it.status == DownloadStatus.Running }
if (running.isEmpty() || running.any { it.total <= 0 }) return DownloadProgress(bytes = 0, total = 0)
return DownloadProgress(
bytes = running.sumOf { it.bytes },
total = running.sumOf { it.total },
)
}
internal fun ListQuery.albumSort(): AlbumSort = AlbumSort.entries.firstOrNull { it.name == sort } ?: AlbumSort.Name internal fun ListQuery.albumSort(): AlbumSort = AlbumSort.entries.firstOrNull { it.name == sort } ?: AlbumSort.Name
internal fun ListQuery.artistSort(): ArtistSort = ArtistSort.entries.firstOrNull { it.name == sort } ?: ArtistSort.Name internal fun ListQuery.artistSort(): ArtistSort = ArtistSort.entries.firstOrNull { it.name == sort } ?: ArtistSort.Name
-1
View File
@@ -176,7 +176,6 @@
<string name="filter_downloaded">Downloaded</string> <string name="filter_downloaded">Downloaded</string>
<string name="close_search">Close search</string> <string name="close_search">Close search</string>
<string name="offline_mode_status">Offline mode; tap to go online</string> <string name="offline_mode_status">Offline mode; tap to go online</string>
<string name="downloads_in_progress">Downloads in progress; tap to open the download manager</string>
<string name="shuffle_artist">Shuffle artist</string> <string name="shuffle_artist">Shuffle artist</string>
<string name="status_playing">Playing</string> <string name="status_playing">Playing</string>
@@ -1,10 +1,7 @@
package com.subtracks.ui.components package com.subtracks.ui.components
import com.subtracks.data.model.DownloadProgress
import com.subtracks.data.model.ListDownloadStatus import com.subtracks.data.model.ListDownloadStatus
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
@@ -38,14 +35,4 @@ class DownloadIndicatorTest {
fun aFullyDownloadedListIsNotDownloading() { fun aFullyDownloadedListIsNotDownloading() {
assertFalse(ListDownloadStatus(total = 4, downloaded = 4).isDownloading()) assertFalse(ListDownloadStatus(total = 4, downloaded = 4).isDownloading())
} }
@Test
fun totalDownloadProgressCombinesEveryInFlightTransfer() {
assertEquals(0.4f, DownloadProgress(bytes = 40, total = 100).fraction)
}
@Test
fun queuedDownloadsWithoutASizeShowAnIndeterminateRing() {
assertNull(DownloadProgress(bytes = 0, total = 0).fraction)
}
} }
@@ -1,15 +1,11 @@
package com.subtracks.ui.library package com.subtracks.ui.library
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.model.DownloadProgress
import com.subtracks.data.model.DownloadStatus
import com.subtracks.data.model.SongDownload
import com.subtracks.data.prefs.AlbumSort import com.subtracks.data.prefs.AlbumSort
import com.subtracks.data.prefs.ArtistSort import com.subtracks.data.prefs.ArtistSort
import com.subtracks.data.prefs.ListQuery import com.subtracks.data.prefs.ListQuery
import com.subtracks.data.prefs.PlaylistSort import com.subtracks.data.prefs.PlaylistSort
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -28,39 +24,4 @@ class LibraryViewModelTest {
assertEquals(ArtistSort.Name, ListQuery("LegacyArtistOrder").artistSort()) assertEquals(ArtistSort.Name, ListQuery("LegacyArtistOrder").artistSort())
assertEquals(PlaylistSort.Name, ListQuery("LegacyPlaylistOrder").playlistSort()) assertEquals(PlaylistSort.Name, ListQuery("LegacyPlaylistOrder").playlistSort())
} }
@Test
fun downloadProgressOnlyCountsTheActiveSource() {
val rows = listOf(download(1, bytes = 40, total = 100), download(2, bytes = 10, total = 20))
assertEquals(DownloadProgress(bytes = 40, total = 100), downloadProgress(rows, sourceId = 1))
}
@Test
fun anotherSourcesDownloadsDoNotShowAProgressRing() {
assertNull(downloadProgress(listOf(download(2, bytes = 10, total = 20)), sourceId = 1))
}
@Test
fun aTransferWithoutAKnownSizeIsIndeterminate() {
val rows =
listOf(
download(1, bytes = 100, total = 100),
download(1, bytes = 20, total = 0).copy(songId = "s2"),
)
assertEquals(DownloadProgress(bytes = 0, total = 0), downloadProgress(rows, sourceId = 1))
}
private fun download(
sourceId: Long,
bytes: Long,
total: Long,
) = SongDownload(
sourceId = sourceId,
songId = "s1",
status = DownloadStatus.Running,
bytes = bytes,
total = total,
)
} }