diff --git a/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckCardSheet.kt b/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckCardSheet.kt index 59a3a7d..0f487dd 100644 --- a/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckCardSheet.kt +++ b/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckCardSheet.kt @@ -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() diff --git a/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckRepository.kt b/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckRepository.kt index fb7c78d..f057ed2 100644 --- a/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckRepository.kt +++ b/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckRepository.kt @@ -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 { diff --git a/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckScreen.kt b/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckScreen.kt index 0c4a4b4..7dd7ac8 100644 --- a/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckScreen.kt +++ b/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckScreen.kt @@ -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(null) } + var renameStackTarget by remember { mutableStateOf(null) } + var deleteStackTarget by remember { mutableStateOf(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 -> diff --git a/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckViewModel.kt b/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckViewModel.kt index 1668b40..8665fef 100644 --- a/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckViewModel.kt +++ b/feature/deck/src/main/java/ru/forbion/f7cloud/feature/deck/DeckViewModel.kt @@ -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,