Release GPS2Audio v2.5.44 POI category fix
This commit is contained in:
@@ -10,6 +10,8 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -20,6 +22,7 @@ import androidx.compose.ui.unit.dp
|
||||
import de.waypointaudio.R
|
||||
import de.waypointaudio.data.MusicSourceType
|
||||
import de.waypointaudio.data.PlaylistItem
|
||||
import de.waypointaudio.data.StreamEntry
|
||||
import de.waypointaudio.data.TourAudioSettings
|
||||
import de.waypointaudio.data.WaypointMusicBehavior
|
||||
import de.waypointaudio.viewmodel.WaypointViewModel
|
||||
@@ -30,7 +33,7 @@ import de.waypointaudio.viewmodel.WaypointViewModel
|
||||
* Öffnet sich über das sichtbare Begleitmusik-Panel oder über das Overflow-Menü;
|
||||
* zeigt alle Optionen für die aktuell gewählte Tour.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun BegleitmusikDialog(
|
||||
tourName: String,
|
||||
@@ -40,19 +43,33 @@ fun BegleitmusikDialog(
|
||||
val context = LocalContext.current
|
||||
val initialSettings by viewModel.musicSettings.collectAsState()
|
||||
val musicPlaying by viewModel.musicPlaying.collectAsState()
|
||||
val audioLibrary by viewModel.audioLibrary.collectAsState()
|
||||
|
||||
// Sub-Dialog: Auswahl aus der Audio-Bibliothek
|
||||
var showLibraryPicker by remember { mutableStateOf(false) }
|
||||
|
||||
// Lokaler Bearbeitungszustand – erst beim Speichern in VM schreiben
|
||||
var enabled by remember(initialSettings) { mutableStateOf(initialSettings.enabled) }
|
||||
var sourceType by remember(initialSettings) { mutableStateOf(initialSettings.sourceType) }
|
||||
var localPlaylist by remember(initialSettings) { mutableStateOf(initialSettings.localPlaylist) }
|
||||
var streamUrl by remember(initialSettings) { mutableStateOf(initialSettings.streamUrl ?: "") }
|
||||
// Stream-Playlist – Legacy streamUrl wird beim Initialisieren in einen Eintrag migriert,
|
||||
// sodass im Dialog immer die neue Repräsentation verwendet wird.
|
||||
var streamPlaylist by remember(initialSettings) {
|
||||
mutableStateOf(initialSettings.effectiveStreamPlaylist)
|
||||
}
|
||||
var behavior by remember(initialSettings) { mutableStateOf(initialSettings.behavior) }
|
||||
var fadeDurationMs by remember(initialSettings) { mutableStateOf(initialSettings.fadeDurationMs) }
|
||||
var duckVolume by remember(initialSettings) { mutableStateOf(initialSettings.duckVolume) }
|
||||
var shuffle by remember(initialSettings) { mutableStateOf(initialSettings.shuffle) }
|
||||
var autoStartAfterWaypoint by remember(initialSettings) { mutableStateOf(initialSettings.autoStartAfterWaypoint) }
|
||||
|
||||
// Eingabezustand für „neuen Stream hinzufügen"
|
||||
var newStreamName by remember(initialSettings) { mutableStateOf("") }
|
||||
var newStreamUrl by remember(initialSettings) { mutableStateOf("") }
|
||||
var newStreamError by remember { mutableStateOf("") }
|
||||
var streamUrlError by remember { mutableStateOf("") }
|
||||
// Index des gerade editierten Stream-Eintrags (-1 = kein Edit-Modus)
|
||||
var editingStreamIndex by remember { mutableStateOf(-1) }
|
||||
|
||||
// Launcher für mehrere Audiodateien
|
||||
val multiPickLauncher = rememberLauncherForActivityResult(
|
||||
@@ -84,23 +101,19 @@ fun BegleitmusikDialog(
|
||||
|
||||
fun validateAndSave(): Boolean {
|
||||
if (enabled && sourceType == MusicSourceType.STREAM_URL) {
|
||||
val url = streamUrl.trim()
|
||||
if (url.isBlank()) {
|
||||
streamUrlError = context.getString(R.string.music_stream_url_empty_error)
|
||||
return false
|
||||
}
|
||||
val lower = url.lowercase()
|
||||
if (!lower.startsWith("http://") && !lower.startsWith("https://")) {
|
||||
streamUrlError = context.getString(R.string.music_stream_url_invalid_error)
|
||||
if (streamPlaylist.isEmpty()) {
|
||||
streamUrlError = context.getString(R.string.music_stream_playlist_empty_error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
streamUrlError = ""
|
||||
// streamUrl-Legacy-Feld leer lassen – Playlist ist jetzt kanonisch.
|
||||
val settings = TourAudioSettings(
|
||||
enabled = enabled,
|
||||
sourceType = sourceType,
|
||||
localPlaylist = localPlaylist,
|
||||
streamUrl = streamUrl.trim().ifBlank { null },
|
||||
streamPlaylist = streamPlaylist,
|
||||
streamUrl = null,
|
||||
behavior = behavior,
|
||||
fadeDurationMs = fadeDurationMs,
|
||||
duckVolume = duckVolume,
|
||||
@@ -111,6 +124,18 @@ fun BegleitmusikDialog(
|
||||
return true
|
||||
}
|
||||
|
||||
fun validateStreamUrlInput(url: String): String? {
|
||||
val trimmed = url.trim()
|
||||
if (trimmed.isBlank()) {
|
||||
return context.getString(R.string.music_stream_url_empty_error)
|
||||
}
|
||||
val lower = trimmed.lowercase()
|
||||
if (!lower.startsWith("http://") && !lower.startsWith("https://")) {
|
||||
return context.getString(R.string.music_stream_url_invalid_error)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
@@ -187,10 +212,16 @@ fun BegleitmusikDialog(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
if (localPlaylist.isEmpty()) {
|
||||
// Hinweis je nach Verfügbarkeit der Audio-Bibliothek anpassen,
|
||||
// damit die User wissen, dass sie auch bereits hinzugefügte
|
||||
// Dateien erneut auswählen können.
|
||||
Text(
|
||||
stringResource(R.string.music_playlist_empty),
|
||||
if (audioLibrary.isEmpty())
|
||||
stringResource(R.string.music_playlist_empty)
|
||||
else
|
||||
stringResource(R.string.music_playlist_empty_with_library),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
} else {
|
||||
localPlaylist.forEachIndexed { idx, item ->
|
||||
@@ -229,7 +260,11 @@ fun BegleitmusikDialog(
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
multiPickLauncher.launch(
|
||||
@@ -241,6 +276,21 @@ fun BegleitmusikDialog(
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(stringResource(R.string.music_playlist_add))
|
||||
}
|
||||
// Aus Bibliothek auswählen (bereits bekannte Audiodateien
|
||||
// wiederverwenden – tour-übergreifend & inkl. Wegpunkt-Audios)
|
||||
if (audioLibrary.isNotEmpty()) {
|
||||
OutlinedButton(
|
||||
onClick = { showLibraryPicker = true }
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.LibraryMusic,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(stringResource(R.string.music_playlist_pick_from_library))
|
||||
}
|
||||
}
|
||||
if (localPlaylist.isNotEmpty()) {
|
||||
OutlinedButton(
|
||||
onClick = { localPlaylist = emptyList() },
|
||||
@@ -266,17 +316,165 @@ fun BegleitmusikDialog(
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Stream-URL ───────────────────────────────────────────────
|
||||
// ─── Stream-Playlist ──────────────────────────────────────────
|
||||
if (sourceType == MusicSourceType.STREAM_URL) {
|
||||
Text(
|
||||
stringResource(R.string.music_stream_playlist_label),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
if (streamPlaylist.isEmpty()) {
|
||||
Text(
|
||||
stringResource(R.string.music_stream_playlist_empty),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
} else {
|
||||
streamPlaylist.forEachIndexed { idx, entry ->
|
||||
val displayLabel = entry.name.ifBlank { entry.url }
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Radio,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = displayLabel,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
if (entry.name.isNotBlank() && entry.url.isNotBlank()) {
|
||||
Text(
|
||||
text = entry.url,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.55f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (idx > 0) {
|
||||
streamPlaylist = streamPlaylist.toMutableList().also { list ->
|
||||
val tmp = list[idx - 1]
|
||||
list[idx - 1] = list[idx]
|
||||
list[idx] = tmp
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = idx > 0,
|
||||
modifier = Modifier.size(32.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.KeyboardArrowUp,
|
||||
contentDescription = stringResource(R.string.music_stream_move_up),
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (idx < streamPlaylist.size - 1) {
|
||||
streamPlaylist = streamPlaylist.toMutableList().also { list ->
|
||||
val tmp = list[idx + 1]
|
||||
list[idx + 1] = list[idx]
|
||||
list[idx] = tmp
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = idx < streamPlaylist.size - 1,
|
||||
modifier = Modifier.size(32.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.KeyboardArrowDown,
|
||||
contentDescription = stringResource(R.string.music_stream_move_down),
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
editingStreamIndex = idx
|
||||
newStreamName = entry.name
|
||||
newStreamUrl = entry.url
|
||||
newStreamError = ""
|
||||
},
|
||||
modifier = Modifier.size(32.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Edit,
|
||||
contentDescription = stringResource(R.string.music_stream_edit),
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
streamPlaylist = streamPlaylist.toMutableList()
|
||||
.also { it.removeAt(idx) }
|
||||
if (editingStreamIndex == idx) {
|
||||
editingStreamIndex = -1
|
||||
newStreamName = ""
|
||||
newStreamUrl = ""
|
||||
newStreamError = ""
|
||||
} else if (editingStreamIndex > idx) {
|
||||
editingStreamIndex -= 1
|
||||
}
|
||||
},
|
||||
modifier = Modifier.size(32.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Close,
|
||||
contentDescription = stringResource(R.string.music_stream_remove),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Anzeige des Stream-Playlist-Validierungsfehlers (z. B. leer beim Save)
|
||||
if (streamUrlError.isNotBlank()) {
|
||||
Text(
|
||||
streamUrlError,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Eingabe: neuer / editierter Stream ───────────────────
|
||||
val editing = editingStreamIndex in streamPlaylist.indices
|
||||
Text(
|
||||
if (editing) stringResource(R.string.music_stream_edit_section_label)
|
||||
else stringResource(R.string.music_stream_add_section_label),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = streamUrl,
|
||||
onValueChange = { streamUrl = it; streamUrlError = "" },
|
||||
value = newStreamName,
|
||||
onValueChange = { newStreamName = it },
|
||||
label = { Text(stringResource(R.string.music_stream_name_label)) },
|
||||
placeholder = { Text(stringResource(R.string.music_stream_name_placeholder)) },
|
||||
singleLine = true,
|
||||
leadingIcon = { Icon(Icons.Filled.Label, null) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = newStreamUrl,
|
||||
onValueChange = { newStreamUrl = it; newStreamError = "" },
|
||||
label = { Text(stringResource(R.string.music_stream_url_label)) },
|
||||
placeholder = { Text("https://stream.example.com/radio") },
|
||||
isError = streamUrlError.isNotBlank(),
|
||||
isError = newStreamError.isNotBlank(),
|
||||
supportingText = {
|
||||
if (streamUrlError.isNotBlank()) {
|
||||
Text(streamUrlError, color = MaterialTheme.colorScheme.error)
|
||||
if (newStreamError.isNotBlank()) {
|
||||
Text(newStreamError, color = MaterialTheme.colorScheme.error)
|
||||
} else {
|
||||
Text(
|
||||
stringResource(R.string.music_stream_url_hint),
|
||||
@@ -288,6 +486,89 @@ fun BegleitmusikDialog(
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
val err = validateStreamUrlInput(newStreamUrl)
|
||||
if (err != null) {
|
||||
newStreamError = err
|
||||
return@OutlinedButton
|
||||
}
|
||||
val entry = StreamEntry(
|
||||
url = newStreamUrl.trim(),
|
||||
name = newStreamName.trim()
|
||||
)
|
||||
streamPlaylist = if (editing) {
|
||||
streamPlaylist.toMutableList().also { list ->
|
||||
list[editingStreamIndex] = entry
|
||||
}
|
||||
} else {
|
||||
streamPlaylist + entry
|
||||
}
|
||||
editingStreamIndex = -1
|
||||
newStreamName = ""
|
||||
newStreamUrl = ""
|
||||
newStreamError = ""
|
||||
streamUrlError = ""
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
if (editing) Icons.Filled.Check else Icons.Filled.Add,
|
||||
null, Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(
|
||||
if (editing) stringResource(R.string.music_stream_save_entry)
|
||||
else stringResource(R.string.music_stream_add_entry)
|
||||
)
|
||||
}
|
||||
if (editing) {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
editingStreamIndex = -1
|
||||
newStreamName = ""
|
||||
newStreamUrl = ""
|
||||
newStreamError = ""
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
if (streamPlaylist.isNotEmpty()) {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
streamPlaylist = emptyList()
|
||||
editingStreamIndex = -1
|
||||
},
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
) {
|
||||
Text(stringResource(R.string.music_stream_clear))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle auch für Stream-Playlist erlauben (sinnvoll bei
|
||||
// mehreren Sendern, die in zufälliger Reihenfolge laufen sollen)
|
||||
if (streamPlaylist.size > 1) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.music_shuffle),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Switch(checked = shuffle, onCheckedChange = { shuffle = it })
|
||||
}
|
||||
}
|
||||
|
||||
// Hinweis zu YouTube/SoundCloud/radio.de
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||
@@ -456,6 +737,135 @@ fun BegleitmusikDialog(
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Sub-Dialog: Bekannte Audiodateien aus der Bibliothek auswählen.
|
||||
if (showLibraryPicker) {
|
||||
LibraryPickerDialog(
|
||||
library = audioLibrary,
|
||||
alreadySelectedUris = localPlaylist.map { it.uriString }.toSet(),
|
||||
onDismiss = { showLibraryPicker = false },
|
||||
onConfirm = { picked ->
|
||||
if (picked.isNotEmpty()) {
|
||||
// URI-Berechtigungen erneut sichern (idempotent für bereits
|
||||
// freigegebene URIs; verhindert Lese-Fehler nach App-Restart)
|
||||
picked.forEach { item ->
|
||||
runCatching {
|
||||
context.contentResolver.takePersistableUriPermission(
|
||||
Uri.parse(item.uriString),
|
||||
android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
}
|
||||
localPlaylist = (localPlaylist + picked).distinctBy { it.uriString }
|
||||
}
|
||||
showLibraryPicker = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-Dialog zur Auswahl von Audiodateien aus der tour-übergreifenden Audio-Bibliothek.
|
||||
*
|
||||
* Zeigt alle bekannten Audiodateien (aus Wegpunkten und allen Begleitmusik-Playlists),
|
||||
* mit Häkchen für die Auswahl. Bereits in der aktuellen Playlist befindliche Dateien
|
||||
* sind vorab angekreuzt und können hier nicht entfernt werden – nur hinzukommende
|
||||
* Auswahl wird übergeben.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun LibraryPickerDialog(
|
||||
library: List<de.waypointaudio.data.PlaylistItem>,
|
||||
alreadySelectedUris: Set<String>,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (List<de.waypointaudio.data.PlaylistItem>) -> Unit
|
||||
) {
|
||||
// Nur Dateien anzeigen, die noch nicht in der aktuellen Playlist sind
|
||||
val available = remember(library, alreadySelectedUris) {
|
||||
library.filter { it.uriString !in alreadySelectedUris }
|
||||
}
|
||||
val checkedUris = remember { mutableStateListOf<String>() }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.music_library_picker_title),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.music_library_picker_subtitle),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
if (available.isEmpty()) {
|
||||
Text(
|
||||
stringResource(R.string.music_library_picker_empty),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
} else {
|
||||
available.forEach { item ->
|
||||
val isChecked = item.uriString in checkedUris
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Checkbox(
|
||||
checked = isChecked,
|
||||
onCheckedChange = { c ->
|
||||
if (c) {
|
||||
if (item.uriString !in checkedUris) checkedUris += item.uriString
|
||||
} else {
|
||||
checkedUris.remove(item.uriString)
|
||||
}
|
||||
}
|
||||
)
|
||||
Icon(
|
||||
Icons.Filled.AudioFile,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text(
|
||||
text = item.displayName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.weight(1f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = checkedUris.isNotEmpty(),
|
||||
onClick = {
|
||||
val picked = available.filter { it.uriString in checkedUris }
|
||||
onConfirm(picked)
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.music_library_picker_add))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
Reference in New Issue
Block a user