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:
@@ -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") }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user