feat: add tab rename and pin/unpin via long-press context menu
Made-with: Cursor
This commit is contained in:
@@ -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") }
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user