Release v2026.05.15.0 — system playlists v2, offline cache rework, CI speedup #48

Merged
bvandeusen merged 262 commits from dev into main 2026-05-15 23:15:48 -04:00
Showing only changes of commit c9bf0479ec - Show all commits
@@ -11,17 +11,14 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -122,12 +119,41 @@ private fun UsageRow(usedBytes: Long) {
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun CapDropdown(label: String, value: Long, onSet: (Long) -> Unit) { private fun CapDropdown(label: String, value: Long, onSet: (Long) -> Unit) {
var expanded by remember { mutableStateOf(false) }
val selectedLabel = CAP_OPTIONS.firstOrNull { it.first == value }?.second val selectedLabel = CAP_OPTIONS.firstOrNull { it.first == value }?.second
?: formatBytes(value) ?: formatBytes(value)
LabeledDropdown(
label = label,
selectedLabel = selectedLabel,
options = CAP_OPTIONS,
onSelect = onSet,
)
}
@Composable
private fun PrefetchDropdown(value: Int, onSet: (Int) -> Unit) {
val selected = if (value in PREFETCH_OPTIONS) value else PREFETCH_OPTIONS[2]
LabeledDropdown(
label = "Pre-fetch ahead",
selectedLabel = "$selected tracks",
options = PREFETCH_OPTIONS.map { it to "$it tracks" },
onSelect = onSet,
)
}
/**
* Label on the left, button-with-current-value on the right that opens
* a DropdownMenu of [options]. Used by both Cap and Prefetch selectors.
*/
@Composable
private fun <T> LabeledDropdown(
label: String,
selectedLabel: String,
options: List<Pair<T, String>>,
onSelect: (T) -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -138,73 +164,18 @@ private fun CapDropdown(label: String, value: Long, onSet: (Long) -> Unit) {
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) )
ExposedDropdownMenuBox( OutlinedButton(onClick = { expanded = true }) {
expanded = expanded, Text(selectedLabel)
onExpandedChange = { expanded = it }, DropdownMenu(
) {
TextField(
value = selectedLabel,
onValueChange = {},
readOnly = true,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier.menuAnchor(),
)
ExposedDropdownMenuDefaults.run {
androidx.compose.material3.ExposedDropdownMenu(
expanded = expanded, expanded = expanded,
onDismissRequest = { expanded = false }, onDismissRequest = { expanded = false },
) { ) {
CAP_OPTIONS.forEach { (bytes, name) -> options.forEach { (value, name) ->
DropdownMenuItem( DropdownMenuItem(
text = { Text(name) }, text = { Text(name) },
onClick = { onClick = {
expanded = false expanded = false
onSet(bytes) onSelect(value)
},
)
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun PrefetchDropdown(value: Int, onSet: (Int) -> Unit) {
var expanded by remember { mutableStateOf(false) }
val selected = if (value in PREFETCH_OPTIONS) value else PREFETCH_OPTIONS[2]
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "Pre-fetch ahead",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f),
)
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
) {
TextField(
value = "$selected tracks",
onValueChange = {},
readOnly = true,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier.menuAnchor(),
)
androidx.compose.material3.ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
PREFETCH_OPTIONS.forEach { n ->
DropdownMenuItem(
text = { Text("$n tracks") },
onClick = {
expanded = false
onSet(n)
}, },
) )
} }