Fix page list: drag reorder, navigation, delete renumbering, state

Drag reorder:
- Separated long-press drag from tap/menu: long-press-and-drag reorders,
  tap opens page, overflow menu (three dots) for delete/export
- Fixed combinedClickable conflict that prevented drag from working

Page navigation:
- View-all-pages state persisted in SharedPreferences, restored on startup
- Cleared when navigating back to editor from page list

Delete renumbering:
- After deleting a page, remaining pages are renumbered sequentially

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 17:28:02 -07:00
parent 2692f0eb0d
commit b18e77177e
5 changed files with 92 additions and 76 deletions

View File

@@ -8,25 +8,24 @@ import net.metacircular.engpad.data.db.EngPadDatabase
class EngPadApp : Application() {
val database: EngPadDatabase by lazy { EngPadDatabase.getInstance(this) }
fun getLastNotebookId(): Long {
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
return prefs.getLong(KEY_LAST_NOTEBOOK, 0)
}
private val prefs by lazy { getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) }
fun setLastNotebookId(id: Long) {
getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
putLong(KEY_LAST_NOTEBOOK, id)
}
}
fun getLastNotebookId(): Long = prefs.getLong(KEY_LAST_NOTEBOOK, 0)
fun clearLastNotebookId() {
getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
fun setLastNotebookId(id: Long) = prefs.edit { putLong(KEY_LAST_NOTEBOOK, id) }
fun clearLastNotebookId() = prefs.edit {
remove(KEY_LAST_NOTEBOOK)
remove(KEY_VIEW_ALL_PAGES)
}
}
fun isViewAllPages(): Boolean = prefs.getBoolean(KEY_VIEW_ALL_PAGES, false)
fun setViewAllPages(value: Boolean) = prefs.edit { putBoolean(KEY_VIEW_ALL_PAGES, value) }
companion object {
private const val PREFS_NAME = "engpad_prefs"
private const val KEY_LAST_NOTEBOOK = "last_notebook_id"
private const val KEY_VIEW_ALL_PAGES = "view_all_pages"
}
}

View File

@@ -13,6 +13,7 @@ class MainActivity : ComponentActivity() {
val app = application as EngPadApp
val database = app.database
val lastNotebookId = app.getLastNotebookId()
val wasViewAllPages = app.isViewAllPages()
setContent {
EngPadTheme {
val navController = rememberNavController()
@@ -20,8 +21,10 @@ class MainActivity : ComponentActivity() {
navController = navController,
database = database,
lastNotebookId = lastNotebookId,
lastViewAllPages = wasViewAllPages,
onNotebookOpened = { app.setLastNotebookId(it) },
onNotebookClosed = { app.clearLastNotebookId() },
onViewAllPagesChanged = { app.setViewAllPages(it) },
)
}
}

View File

@@ -35,15 +35,22 @@ fun EngPadNavGraph(
navController: NavHostController,
database: EngPadDatabase,
lastNotebookId: Long = 0,
lastViewAllPages: Boolean = false,
onNotebookOpened: (Long) -> Unit = {},
onNotebookClosed: () -> Unit = {},
onViewAllPagesChanged: (Boolean) -> Unit = {},
) {
// Auto-navigate to last notebook on startup
var autoNavigated by remember { mutableStateOf(false) }
LaunchedEffect(lastNotebookId) {
if (!autoNavigated && lastNotebookId > 0) {
autoNavigated = true
if (lastViewAllPages) {
navController.navigate(Routes.editor(lastNotebookId))
navController.navigate(Routes.pages(lastNotebookId))
} else {
navController.navigate(Routes.editor(lastNotebookId))
}
}
}
@@ -111,6 +118,7 @@ fun EngPadNavGraph(
navController.popBackStack(Routes.NOTEBOOKS, false)
},
onViewAllPages = {
onViewAllPagesChanged(true)
navController.navigate(Routes.pages(notebookId))
},
)
@@ -145,7 +153,7 @@ fun EngPadNavGraph(
pageSize = pageSize,
database = database,
onPageClick = { pageId, _ ->
// Pass selected page ID back to the editor
onViewAllPagesChanged(false)
navController.previousBackStackEntry
?.savedStateHandle
?.set("selectedPageId", pageId)

View File

@@ -3,8 +3,7 @@ package net.metacircular.engpad.ui.pages
import android.graphics.Paint as AndroidPaint
import android.graphics.Path as AndroidPath
import androidx.compose.foundation.Canvas
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
@@ -12,6 +11,7 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
@@ -24,6 +24,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
@@ -31,8 +32,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
@@ -78,7 +79,6 @@ fun PageListScreen(
var pageToDelete by remember { mutableStateOf<Page?>(null) }
// Maintain an observable mutable list for drag reorder
val reorderablePages = remember { mutableStateListOf<Page>() }
LaunchedEffect(pages) {
reorderablePages.clear()
@@ -151,7 +151,6 @@ fun PageListScreen(
},
modifier = Modifier.longPressDraggableHandle(
onDragStopped = {
// Persist the new order
viewModel.reorderPages(
notebookId,
reorderablePages.map { it.id },
@@ -183,7 +182,6 @@ fun PageListScreen(
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun PageThumbnail(
page: Page,
@@ -205,25 +203,22 @@ private fun PageThumbnail(
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
modifier = modifier
.graphicsLayer {
if (isDragging) {
scaleX = 1.05f
scaleY = 1.05f
alpha = 0.8f
}
}
.then(modifier),
},
) {
Box {
Card(
modifier = Modifier
.fillMaxWidth()
.combinedClickable(
onClick = onClick,
onLongClick = { if (!isDragging) showMenu = true },
),
.clickable(onClick = onClick),
) {
Box {
Canvas(
modifier = Modifier
.fillMaxWidth()
@@ -259,6 +254,17 @@ private fun PageThumbnail(
}
}
}
// Overflow menu button (top-right corner)
Box(modifier = Modifier.align(Alignment.TopEnd)) {
Surface(
onClick = { showMenu = true },
color = androidx.compose.ui.graphics.Color.Transparent,
modifier = Modifier.size(32.dp),
) {
Box(contentAlignment = Alignment.Center) {
Text("\u22EE", style = MaterialTheme.typography.bodyLarge)
}
}
DropdownMenu(
expanded = showMenu,
@@ -266,20 +272,17 @@ private fun PageThumbnail(
) {
DropdownMenuItem(
text = { Text("Export as JPG") },
onClick = {
showMenu = false
onExportJpg()
},
onClick = { showMenu = false; onExportJpg() },
)
DropdownMenuItem(
text = { Text("Delete page") },
onClick = {
showMenu = false
onDelete()
},
onClick = { showMenu = false; onDelete() },
)
}
}
}
}
}
Text(
text = "Page ${page.pageNumber}",
style = MaterialTheme.typography.bodySmall,

View File

@@ -28,6 +28,9 @@ class PageListViewModel(
fun deletePage(pageId: Long) {
viewModelScope.launch {
repository.deletePage(pageId)
// Renumber remaining pages sequentially
val remaining = repository.getPagesList(notebookId)
repository.reorderPages(notebookId, remaining.map { it.id })
}
}