feat(deck): меню колонки — Переименовать / Удалить список (P1 diff)
DeckRepository.renameStack/deleteStack (PUT/DELETE /stacks/{id}); VM renameStack/deleteStack;
overflow-меню (⋯) в шапке колонки → DropdownMenu; диалог переименования (префилл) +
DeckConfirmDialog для удаления. Веб-паритет меню Stack.
This commit is contained in:
@@ -236,8 +236,9 @@ fun DeckTextPromptDialog(
|
||||
confirmText: String,
|
||||
onConfirm: (String) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
initial: String = "",
|
||||
) {
|
||||
var text by remember { mutableStateOf("") }
|
||||
var text by remember { mutableStateOf(initial) }
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Surface(shape = RoundedCornerShape(16.dp), color = Color.White) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
@@ -263,6 +264,33 @@ fun DeckTextPromptDialog(
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun DeckConfirmDialog(
|
||||
title: String,
|
||||
message: String,
|
||||
confirmText: String,
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Surface(shape = RoundedCornerShape(16.dp), color = Color.White) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(
|
||||
title,
|
||||
style = MaterialTheme.typography.titleMedium.copy(fontSize = 18.sp),
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = F7Colors.TextPrimary,
|
||||
)
|
||||
Text(message, style = MaterialTheme.typography.bodyMedium, color = F7Colors.TextSecondary)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
F7SecondaryButton("Отмена", onClick = onDismiss, modifier = Modifier.weight(1f))
|
||||
F7PrimaryButton(text = confirmText, onClick = onConfirm, modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Парсит ISO-строку срока Deck в миллисекунды (для инициализации пикера). */
|
||||
internal fun parseDeckDueMillis(iso: String): Long? = runCatching {
|
||||
Instant.parse(iso).toEpochMilli()
|
||||
|
||||
@@ -215,6 +215,15 @@ class DeckRepository {
|
||||
return DeckStack(id = id, boardId = boardId, title = title, order = 999, cards = emptyList())
|
||||
}
|
||||
|
||||
fun renameStack(session: AuthSession, stack: DeckStack, title: String) {
|
||||
val body = JSONObject().put("title", title).put("boardId", stack.boardId).put("order", stack.order)
|
||||
sendJson(session, "PUT", "${apiBase(session)}/stacks/${stack.id}", body)
|
||||
}
|
||||
|
||||
fun deleteStack(session: AuthSession, stackId: Int) {
|
||||
sendJson(session, "DELETE", "${apiBase(session)}/stacks/$stackId", null)
|
||||
}
|
||||
|
||||
// --- HTTP ---
|
||||
|
||||
private fun getJson(client: OkHttpClient, url: String): Any {
|
||||
|
||||
@@ -22,7 +22,10 @@ import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -62,6 +65,8 @@ fun DeckScreen(
|
||||
val state by vm.state.collectAsState()
|
||||
var addStackOpen by remember { mutableStateOf(false) }
|
||||
var addCardStackId by remember { mutableStateOf<Int?>(null) }
|
||||
var renameStackTarget by remember { mutableStateOf<DeckStack?>(null) }
|
||||
var deleteStackTarget by remember { mutableStateOf<DeckStack?>(null) }
|
||||
|
||||
LaunchedEffect(session.serverUrl, session.username) { vm.load(session) }
|
||||
LaunchedEffect(openCardId) {
|
||||
@@ -110,6 +115,8 @@ fun DeckScreen(
|
||||
onCardClick = { vm.openCard(it) },
|
||||
onToggleDone = { vm.toggleDone(session, it) },
|
||||
onAddCard = { addCardStackId = stack.id },
|
||||
onRename = { renameStackTarget = stack },
|
||||
onDelete = { deleteStackTarget = stack },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -165,6 +172,25 @@ fun DeckScreen(
|
||||
onDismiss = { addCardStackId = null },
|
||||
)
|
||||
}
|
||||
renameStackTarget?.let { stack ->
|
||||
DeckTextPromptDialog(
|
||||
title = "Переименовать колонку",
|
||||
label = "Название колонки",
|
||||
confirmText = "Сохранить",
|
||||
initial = stack.title,
|
||||
onConfirm = { vm.renameStack(session, stack, it); renameStackTarget = null },
|
||||
onDismiss = { renameStackTarget = null },
|
||||
)
|
||||
}
|
||||
deleteStackTarget?.let { stack ->
|
||||
DeckConfirmDialog(
|
||||
title = "Удалить колонку?",
|
||||
message = "Колонка «${stack.title}» и её карточки будут удалены.",
|
||||
confirmText = "Удалить",
|
||||
onConfirm = { vm.deleteStack(session, stack); deleteStackTarget = null },
|
||||
onDismiss = { deleteStackTarget = null },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -206,7 +232,10 @@ private fun DeckStackSection(
|
||||
onCardClick: (DeckCard) -> Unit,
|
||||
onToggleDone: (DeckCard) -> Unit,
|
||||
onAddCard: () -> Unit,
|
||||
onRename: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
) {
|
||||
var menuOpen by remember { mutableStateOf(false) }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -240,6 +269,28 @@ private fun DeckStackSection(
|
||||
) {
|
||||
Icon(Icons.Filled.Add, contentDescription = "Добавить карточку", tint = F7Colors.Primary, modifier = Modifier.size(18.dp))
|
||||
}
|
||||
Box {
|
||||
Icon(
|
||||
Icons.Filled.MoreVert,
|
||||
contentDescription = "Меню колонки",
|
||||
tint = F7Colors.TextSecondary,
|
||||
modifier = Modifier
|
||||
.size(28.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable { menuOpen = true }
|
||||
.padding(5.dp),
|
||||
)
|
||||
DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Переименовать") },
|
||||
onClick = { menuOpen = false; onRename() },
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Удалить список") },
|
||||
onClick = { menuOpen = false; onDelete() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.cards.forEach { card ->
|
||||
|
||||
@@ -124,6 +124,12 @@ class DeckViewModel(
|
||||
mutate(session) { repository.createStack(session, boardId, title.trim()) }
|
||||
}
|
||||
|
||||
fun renameStack(session: AuthSession, stack: DeckStack, title: String) =
|
||||
mutate(session) { repository.renameStack(session, stack, title.trim()) }
|
||||
|
||||
fun deleteStack(session: AuthSession, stack: DeckStack) =
|
||||
mutate(session) { repository.deleteStack(session, stack.id) }
|
||||
|
||||
private fun mutate(
|
||||
session: AuthSession,
|
||||
closeCardAfter: Boolean = false,
|
||||
|
||||
Reference in New Issue
Block a user