Replace notebook long-press with overflow menu (rename/delete)

- Three-dot overflow button on each notebook card
- Dropdown menu with Rename and Delete options
- Rename dialog with title field (keyboard defaults to Title Case)
- Removed combinedClickable — tap opens notebook, menu for actions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 19:20:02 -07:00
parent 1e13361e7e
commit a7c57b56be
2 changed files with 99 additions and 30 deletions

View File

@@ -1,7 +1,6 @@
package net.metacircular.engpad.ui.notebooks
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -69,6 +68,7 @@ fun NotebookListScreen(
val notebooks by viewModel.notebooks.collectAsState()
var showCreateDialog by remember { mutableStateOf(false) }
var notebookToDelete by remember { mutableStateOf<Notebook?>(null) }
var notebookToRename by remember { mutableStateOf<Notebook?>(null) }
var filterText by remember { mutableStateOf("") }
var sortField by remember { mutableStateOf(SortField.LAST_EDITED) }
@@ -169,7 +169,8 @@ fun NotebookListScreen(
NotebookItem(
notebook = notebook,
onClick = { onNotebookClick(notebook.id) },
onLongClick = { notebookToDelete = notebook },
onRename = { notebookToRename = notebook },
onDelete = { notebookToDelete = notebook },
)
}
}
@@ -197,43 +198,75 @@ fun NotebookListScreen(
},
)
}
notebookToRename?.let { notebook ->
RenameNotebookDialog(
notebook = notebook,
onDismiss = { notebookToRename = null },
onRename = { newTitle ->
viewModel.renameNotebook(notebook.id, newTitle)
notebookToRename = null
},
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun NotebookItem(
notebook: Notebook,
onClick: () -> Unit,
onLongClick: () -> Unit,
onRename: () -> Unit,
onDelete: () -> Unit,
) {
val dateFormat = remember { SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US) }
var showMenu by remember { mutableStateOf(false) }
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
),
.clickable(onClick = onClick),
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = notebook.title,
style = MaterialTheme.typography.titleMedium,
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Row(
modifier = Modifier.padding(start = 16.dp, top = 16.dp, bottom = 16.dp, end = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = notebook.pageSize.lowercase().replaceFirstChar { it.uppercase() },
style = MaterialTheme.typography.bodySmall,
)
Text(
text = dateFormat.format(Date(notebook.updatedAt)),
style = MaterialTheme.typography.bodySmall,
text = notebook.title,
style = MaterialTheme.typography.titleMedium,
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = notebook.pageSize.lowercase().replaceFirstChar { it.uppercase() },
style = MaterialTheme.typography.bodySmall,
)
Text(
text = dateFormat.format(Date(notebook.updatedAt)),
style = MaterialTheme.typography.bodySmall,
)
}
}
Box {
androidx.compose.material3.IconButton(onClick = { showMenu = true }) {
Text("\u22EE", style = MaterialTheme.typography.bodyLarge)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
) {
DropdownMenuItem(
text = { Text("Rename") },
onClick = { showMenu = false; onRename() },
)
DropdownMenuItem(
text = { Text("Delete") },
onClick = { showMenu = false; onDelete() },
)
}
}
}
}
@@ -312,14 +345,44 @@ private fun DeleteNotebookDialog(
title = { Text("Delete Notebook") },
text = { Text("Delete \"${notebook.title}\"? This cannot be undone.") },
confirmButton = {
TextButton(onClick = onConfirm) {
Text("Delete")
}
TextButton(onClick = onConfirm) { Text("Delete") }
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Cancel")
}
TextButton(onClick = onDismiss) { Text("Cancel") }
},
)
}
@Composable
private fun RenameNotebookDialog(
notebook: Notebook,
onDismiss: () -> Unit,
onRename: (String) -> Unit,
) {
var title by remember { mutableStateOf(notebook.title) }
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Rename Notebook") },
text = {
OutlinedTextField(
value = title,
onValueChange = { title = it },
label = { Text("Title") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Words,
),
modifier = Modifier.fillMaxWidth(),
)
},
confirmButton = {
TextButton(
onClick = { onRename(title.ifBlank { notebook.title }) },
) { Text("Rename") }
},
dismissButton = {
TextButton(onClick = onDismiss) { Text("Cancel") }
},
)
}

View File

@@ -29,6 +29,12 @@ class NotebookListViewModel(
}
}
fun renameNotebook(id: Long, title: String) {
viewModelScope.launch {
repository.updateTitle(id, title)
}
}
class Factory(private val repository: NotebookRepository) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {