Fix page selection from page list, add library filter and sort
Page selection fix:
- Pass selected page ID as a route parameter (editor/{notebookId}?pageId=X)
instead of savedStateHandle, which was unreliable
- Pop to notebook list then navigate to editor with explicit page ID
- Eliminates stale page selection bug
Library filter/sort:
- Text filter field filters notebooks by name (case-insensitive)
- Sort dropdown: Name, Last Edited, Last Opened
- Clicking the current sort field toggles asc/desc direction
- Defaults to Last Edited descending
- Shows "No matches" when filter has no results vs "No notebooks yet"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,10 +22,12 @@ import net.metacircular.engpad.ui.pages.PageListScreen
|
||||
|
||||
object Routes {
|
||||
const val NOTEBOOKS = "notebooks"
|
||||
const val EDITOR = "editor/{notebookId}"
|
||||
const val EDITOR = "editor/{notebookId}?pageId={pageId}"
|
||||
const val PAGES = "pages/{notebookId}"
|
||||
|
||||
fun editor(notebookId: Long) = "editor/$notebookId"
|
||||
fun editor(notebookId: Long, pageId: Long = 0) =
|
||||
if (pageId > 0) "editor/$notebookId?pageId=$pageId"
|
||||
else "editor/$notebookId"
|
||||
fun pages(notebookId: Long) = "pages/$notebookId"
|
||||
}
|
||||
|
||||
@@ -65,9 +67,13 @@ fun EngPadNavGraph(
|
||||
}
|
||||
composable(
|
||||
route = Routes.EDITOR,
|
||||
arguments = listOf(navArgument("notebookId") { type = NavType.LongType }),
|
||||
arguments = listOf(
|
||||
navArgument("notebookId") { type = NavType.LongType },
|
||||
navArgument("pageId") { type = NavType.LongType; defaultValue = 0L },
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val notebookId = backStackEntry.arguments?.getLong("notebookId") ?: return@composable
|
||||
val requestedPageId = backStackEntry.arguments?.getLong("pageId") ?: 0L
|
||||
val notebookRepo = remember {
|
||||
NotebookRepository(database.notebookDao(), database.pageDao())
|
||||
}
|
||||
@@ -78,27 +84,23 @@ fun EngPadNavGraph(
|
||||
var pageSize by remember { mutableStateOf<PageSize?>(null) }
|
||||
var initialPageId by remember { mutableStateOf<Long?>(null) }
|
||||
|
||||
LaunchedEffect(notebookId) {
|
||||
LaunchedEffect(notebookId, requestedPageId) {
|
||||
onNotebookOpened(notebookId)
|
||||
val notebook = notebookRepo.getById(notebookId) ?: return@LaunchedEffect
|
||||
pageSize = PageSize.fromString(notebook.pageSize)
|
||||
// Use last page or fall back to first page
|
||||
val pages = pageRepo.getPagesList(notebookId)
|
||||
initialPageId = if (notebook.lastPageId > 0 && pages.any { it.id == notebook.lastPageId }) {
|
||||
notebook.lastPageId
|
||||
} else {
|
||||
pages.firstOrNull()?.id ?: return@LaunchedEffect
|
||||
initialPageId = when {
|
||||
// Explicit page requested (from page list click)
|
||||
requestedPageId > 0 && pages.any { it.id == requestedPageId } ->
|
||||
requestedPageId
|
||||
// Resume last visited page
|
||||
notebook.lastPageId > 0 && pages.any { it.id == notebook.lastPageId } ->
|
||||
notebook.lastPageId
|
||||
// Fall back to first page
|
||||
else -> pages.firstOrNull()?.id ?: return@LaunchedEffect
|
||||
}
|
||||
}
|
||||
|
||||
// Check for page selection from the page list screen
|
||||
val savedState = backStackEntry.savedStateHandle
|
||||
val selectedPageId = savedState.get<Long>("selectedPageId")
|
||||
if (selectedPageId != null && selectedPageId > 0) {
|
||||
initialPageId = selectedPageId
|
||||
savedState.remove<Long>("selectedPageId")
|
||||
}
|
||||
|
||||
val ps = pageSize
|
||||
val pid = initialPageId
|
||||
if (ps != null && pid != null) {
|
||||
@@ -150,10 +152,10 @@ fun EngPadNavGraph(
|
||||
database = database,
|
||||
onPageClick = { pageId, _ ->
|
||||
onViewAllPagesChanged(false)
|
||||
navController.previousBackStackEntry
|
||||
?.savedStateHandle
|
||||
?.set("selectedPageId", pageId)
|
||||
navController.popBackStack()
|
||||
// Navigate to editor with explicit page ID
|
||||
// Pop both the page list and the old editor, then push new editor
|
||||
navController.popBackStack(Routes.NOTEBOOKS, false)
|
||||
navController.navigate(Routes.editor(notebookId, pageId))
|
||||
},
|
||||
onClose = {
|
||||
onNotebookClosed()
|
||||
|
||||
@@ -3,16 +3,21 @@ package net.metacircular.engpad.ui.notebooks
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -41,6 +46,14 @@ import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
private enum class SortField(val label: String) {
|
||||
NAME("Name"),
|
||||
LAST_EDITED("Last Edited"),
|
||||
LAST_OPENED("Last Opened"),
|
||||
}
|
||||
|
||||
private enum class SortDir { ASC, DESC }
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NotebookListScreen(
|
||||
@@ -57,6 +70,23 @@ fun NotebookListScreen(
|
||||
var showCreateDialog by remember { mutableStateOf(false) }
|
||||
var notebookToDelete by remember { mutableStateOf<Notebook?>(null) }
|
||||
|
||||
var filterText by remember { mutableStateOf("") }
|
||||
var sortField by remember { mutableStateOf(SortField.LAST_EDITED) }
|
||||
var sortDir by remember { mutableStateOf(SortDir.DESC) }
|
||||
|
||||
// Apply filter and sort
|
||||
val displayedNotebooks = remember(notebooks, filterText, sortField, sortDir) {
|
||||
var list = if (filterText.isBlank()) notebooks
|
||||
else notebooks.filter { it.title.contains(filterText, ignoreCase = true) }
|
||||
|
||||
list = when (sortField) {
|
||||
SortField.NAME -> list.sortedBy { it.title.lowercase() }
|
||||
SortField.LAST_EDITED -> list.sortedBy { it.updatedAt }
|
||||
SortField.LAST_OPENED -> list.sortedBy { it.updatedAt }
|
||||
}
|
||||
if (sortDir == SortDir.DESC) list.reversed() else list
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(title = { Text("Engineering Pad :: Library") })
|
||||
@@ -67,29 +97,81 @@ fun NotebookListScreen(
|
||||
}
|
||||
},
|
||||
) { padding ->
|
||||
if (notebooks.isEmpty()) {
|
||||
Column(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
) {
|
||||
// Filter and sort bar
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text("No notebooks yet", style = MaterialTheme.typography.bodyLarge)
|
||||
Text("Tap + to create one", style = MaterialTheme.typography.bodyMedium)
|
||||
OutlinedTextField(
|
||||
value = filterText,
|
||||
onValueChange = { filterText = it },
|
||||
label = { Text("Filter") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
// Sort dropdown
|
||||
Box {
|
||||
var showSortMenu by remember { mutableStateOf(false) }
|
||||
val dirArrow = if (sortDir == SortDir.ASC) "\u2191" else "\u2193"
|
||||
TextButton(onClick = { showSortMenu = true }) {
|
||||
Text("${sortField.label} $dirArrow")
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showSortMenu,
|
||||
onDismissRequest = { showSortMenu = false },
|
||||
) {
|
||||
SortField.entries.forEach { field ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
val check = if (field == sortField) "\u2713 " else " "
|
||||
Text("$check${field.label}")
|
||||
},
|
||||
onClick = {
|
||||
if (field == sortField) {
|
||||
// Toggle direction
|
||||
sortDir = if (sortDir == SortDir.ASC) SortDir.DESC else SortDir.ASC
|
||||
} else {
|
||||
sortField = field
|
||||
sortDir = if (field == SortField.NAME) SortDir.ASC else SortDir.DESC
|
||||
}
|
||||
showSortMenu = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
) {
|
||||
items(notebooks, key = { it.id }) { notebook ->
|
||||
NotebookItem(
|
||||
notebook = notebook,
|
||||
onClick = { onNotebookClick(notebook.id) },
|
||||
onLongClick = { notebookToDelete = notebook },
|
||||
)
|
||||
|
||||
if (displayedNotebooks.isEmpty()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
if (notebooks.isEmpty()) {
|
||||
Text("No notebooks yet", style = MaterialTheme.typography.bodyLarge)
|
||||
Text("Tap + to create one", style = MaterialTheme.typography.bodyMedium)
|
||||
} else {
|
||||
Text("No matches", style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
items(displayedNotebooks, key = { it.id }) { notebook ->
|
||||
NotebookItem(
|
||||
notebook = notebook,
|
||||
onClick = { onNotebookClick(notebook.id) },
|
||||
onLongClick = { notebookToDelete = notebook },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user