feat: add tab rename and pin/unpin via long-press context menu

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-11 16:27:13 -04:00
parent 973bb9e7fe
commit 2d3b0cea7a
3 changed files with 114 additions and 13 deletions

View File

@@ -0,0 +1,42 @@
package xyz.cottongin.radio247.ui.screens.stationlist
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@Composable
fun RenamePlaylistDialog(
currentName: String,
onDismiss: () -> Unit,
onConfirm: (String) -> Unit
) {
var name by remember { mutableStateOf(currentName) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Rename playlist") },
text = {
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Playlist name") },
singleLine = true
)
},
confirmButton = {
TextButton(
onClick = { onConfirm(name) },
enabled = name.isNotBlank()
) { Text("Rename") }
},
dismissButton = {
TextButton(onClick = onDismiss) { Text("Cancel") }
}
)
}

View File

@@ -68,7 +68,7 @@ import xyz.cottongin.radio247.service.StreamResolver
import org.json.JSONArray
import xyz.cottongin.radio247.ui.components.MiniPlayer
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun StationListScreen(
onNavigateToNowPlaying: () -> Unit,
@@ -90,6 +90,7 @@ fun StationListScreen(
var stationForQuality by remember { mutableStateOf<Station?>(null) }
var qualityStreams by remember { mutableStateOf<List<StationStream>>(emptyList()) }
var qualityCurrentOrder by remember { mutableStateOf<List<String>>(emptyList()) }
var tabToRename by remember { mutableStateOf<TabInfo?>(null) }
val importLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
@@ -167,17 +168,47 @@ fun StationListScreen(
edgePadding = 16.dp
) {
viewState.tabs.forEachIndexed { index, tab ->
Tab(
selected = viewState.selectedTabIndex == index,
onClick = { viewModel.selectTab(index) },
text = {
Text(
text = tab.label,
maxLines = 1,
overflow = TextOverflow.Ellipsis
var showTabMenu by remember { mutableStateOf(false) }
Box {
Tab(
selected = viewState.selectedTabIndex == index,
onClick = { viewModel.selectTab(index) },
text = {
Text(
text = tab.label,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
modifier = Modifier.combinedClickable(
onClick = { viewModel.selectTab(index) },
onLongClick = { if (tab.playlist != null) showTabMenu = true }
)
)
DropdownMenu(
expanded = showTabMenu,
onDismissRequest = { showTabMenu = false }
) {
if (tab.playlist != null && !tab.isBuiltIn) {
DropdownMenuItem(
text = { Text("Rename") },
onClick = {
showTabMenu = false
tabToRename = tab
}
)
}
if (tab.playlist != null) {
DropdownMenuItem(
text = { Text(if (tab.playlist.pinned) "Unpin" else "Pin") },
onClick = {
showTabMenu = false
viewModel.togglePinned(tab.playlist)
}
)
}
}
)
}
}
}
}
@@ -331,6 +362,17 @@ fun StationListScreen(
onConfirm = { name -> viewModel.confirmImport(name) }
)
}
tabToRename?.let { tab ->
RenamePlaylistDialog(
currentName = tab.label,
onDismiss = { tabToRename = null },
onConfirm = { newName ->
tab.playlist?.let { viewModel.renamePlaylist(it, newName) }
tabToRename = null
}
)
}
}
@Composable

View File

@@ -136,10 +136,27 @@ class StationListViewModel(application: Application) : AndroidViewModel(applicat
private fun buildTabs(playlists: List<Playlist>): List<TabInfo> {
val myStations = TabInfo(playlist = null, label = "My Stations", isBuiltIn = false)
val playlistTabs = playlists
.sortedWith(compareByDescending<Playlist> { it.isBuiltIn }.thenBy { it.sortOrder })
val pinned = playlists
.filter { it.pinned }
.sortedBy { it.sortOrder }
.map { TabInfo(playlist = it, label = it.name, isBuiltIn = it.isBuiltIn) }
return listOf(myStations) + playlistTabs
val unpinned = playlists
.filter { !it.pinned }
.sortedBy { it.sortOrder }
.map { TabInfo(playlist = it, label = it.name, isBuiltIn = it.isBuiltIn) }
return pinned + listOf(myStations) + unpinned
}
fun renamePlaylist(playlist: Playlist, newName: String) {
viewModelScope.launch {
playlistDao.rename(playlist.id, newName)
}
}
fun togglePinned(playlist: Playlist) {
viewModelScope.launch {
playlistDao.updatePinned(playlist.id, !playlist.pinned)
}
}
private fun applySortMode(