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:
@@ -8,25 +8,24 @@ import net.metacircular.engpad.data.db.EngPadDatabase
|
|||||||
class EngPadApp : Application() {
|
class EngPadApp : Application() {
|
||||||
val database: EngPadDatabase by lazy { EngPadDatabase.getInstance(this) }
|
val database: EngPadDatabase by lazy { EngPadDatabase.getInstance(this) }
|
||||||
|
|
||||||
fun getLastNotebookId(): Long {
|
private val prefs by lazy { getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) }
|
||||||
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
||||||
return prefs.getLong(KEY_LAST_NOTEBOOK, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setLastNotebookId(id: Long) {
|
fun getLastNotebookId(): Long = prefs.getLong(KEY_LAST_NOTEBOOK, 0)
|
||||||
getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
|
|
||||||
putLong(KEY_LAST_NOTEBOOK, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearLastNotebookId() {
|
fun setLastNotebookId(id: Long) = prefs.edit { putLong(KEY_LAST_NOTEBOOK, id) }
|
||||||
getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit {
|
|
||||||
|
fun clearLastNotebookId() = prefs.edit {
|
||||||
remove(KEY_LAST_NOTEBOOK)
|
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 {
|
companion object {
|
||||||
private const val PREFS_NAME = "engpad_prefs"
|
private const val PREFS_NAME = "engpad_prefs"
|
||||||
private const val KEY_LAST_NOTEBOOK = "last_notebook_id"
|
private const val KEY_LAST_NOTEBOOK = "last_notebook_id"
|
||||||
|
private const val KEY_VIEW_ALL_PAGES = "view_all_pages"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
val app = application as EngPadApp
|
val app = application as EngPadApp
|
||||||
val database = app.database
|
val database = app.database
|
||||||
val lastNotebookId = app.getLastNotebookId()
|
val lastNotebookId = app.getLastNotebookId()
|
||||||
|
val wasViewAllPages = app.isViewAllPages()
|
||||||
setContent {
|
setContent {
|
||||||
EngPadTheme {
|
EngPadTheme {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
@@ -20,8 +21,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
navController = navController,
|
navController = navController,
|
||||||
database = database,
|
database = database,
|
||||||
lastNotebookId = lastNotebookId,
|
lastNotebookId = lastNotebookId,
|
||||||
|
lastViewAllPages = wasViewAllPages,
|
||||||
onNotebookOpened = { app.setLastNotebookId(it) },
|
onNotebookOpened = { app.setLastNotebookId(it) },
|
||||||
onNotebookClosed = { app.clearLastNotebookId() },
|
onNotebookClosed = { app.clearLastNotebookId() },
|
||||||
|
onViewAllPagesChanged = { app.setViewAllPages(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,15 +35,22 @@ fun EngPadNavGraph(
|
|||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
database: EngPadDatabase,
|
database: EngPadDatabase,
|
||||||
lastNotebookId: Long = 0,
|
lastNotebookId: Long = 0,
|
||||||
|
lastViewAllPages: Boolean = false,
|
||||||
onNotebookOpened: (Long) -> Unit = {},
|
onNotebookOpened: (Long) -> Unit = {},
|
||||||
onNotebookClosed: () -> Unit = {},
|
onNotebookClosed: () -> Unit = {},
|
||||||
|
onViewAllPagesChanged: (Boolean) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
// Auto-navigate to last notebook on startup
|
// Auto-navigate to last notebook on startup
|
||||||
var autoNavigated by remember { mutableStateOf(false) }
|
var autoNavigated by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(lastNotebookId) {
|
LaunchedEffect(lastNotebookId) {
|
||||||
if (!autoNavigated && lastNotebookId > 0) {
|
if (!autoNavigated && lastNotebookId > 0) {
|
||||||
autoNavigated = true
|
autoNavigated = true
|
||||||
|
if (lastViewAllPages) {
|
||||||
navController.navigate(Routes.editor(lastNotebookId))
|
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)
|
navController.popBackStack(Routes.NOTEBOOKS, false)
|
||||||
},
|
},
|
||||||
onViewAllPages = {
|
onViewAllPages = {
|
||||||
|
onViewAllPagesChanged(true)
|
||||||
navController.navigate(Routes.pages(notebookId))
|
navController.navigate(Routes.pages(notebookId))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -145,7 +153,7 @@ fun EngPadNavGraph(
|
|||||||
pageSize = pageSize,
|
pageSize = pageSize,
|
||||||
database = database,
|
database = database,
|
||||||
onPageClick = { pageId, _ ->
|
onPageClick = { pageId, _ ->
|
||||||
// Pass selected page ID back to the editor
|
onViewAllPagesChanged(false)
|
||||||
navController.previousBackStackEntry
|
navController.previousBackStackEntry
|
||||||
?.savedStateHandle
|
?.savedStateHandle
|
||||||
?.set("selectedPageId", pageId)
|
?.set("selectedPageId", pageId)
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ package net.metacircular.engpad.ui.pages
|
|||||||
import android.graphics.Paint as AndroidPaint
|
import android.graphics.Paint as AndroidPaint
|
||||||
import android.graphics.Path as AndroidPath
|
import android.graphics.Path as AndroidPath
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
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.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
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.FloatingActionButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
@@ -31,8 +32,8 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -78,7 +79,6 @@ fun PageListScreen(
|
|||||||
|
|
||||||
var pageToDelete by remember { mutableStateOf<Page?>(null) }
|
var pageToDelete by remember { mutableStateOf<Page?>(null) }
|
||||||
|
|
||||||
// Maintain an observable mutable list for drag reorder
|
|
||||||
val reorderablePages = remember { mutableStateListOf<Page>() }
|
val reorderablePages = remember { mutableStateListOf<Page>() }
|
||||||
LaunchedEffect(pages) {
|
LaunchedEffect(pages) {
|
||||||
reorderablePages.clear()
|
reorderablePages.clear()
|
||||||
@@ -151,7 +151,6 @@ fun PageListScreen(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.longPressDraggableHandle(
|
modifier = Modifier.longPressDraggableHandle(
|
||||||
onDragStopped = {
|
onDragStopped = {
|
||||||
// Persist the new order
|
|
||||||
viewModel.reorderPages(
|
viewModel.reorderPages(
|
||||||
notebookId,
|
notebookId,
|
||||||
reorderablePages.map { it.id },
|
reorderablePages.map { it.id },
|
||||||
@@ -183,7 +182,6 @@ fun PageListScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PageThumbnail(
|
private fun PageThumbnail(
|
||||||
page: Page,
|
page: Page,
|
||||||
@@ -205,25 +203,22 @@ private fun PageThumbnail(
|
|||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
scaleX = 1.05f
|
scaleX = 1.05f
|
||||||
scaleY = 1.05f
|
scaleY = 1.05f
|
||||||
alpha = 0.8f
|
alpha = 0.8f
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
.then(modifier),
|
|
||||||
) {
|
) {
|
||||||
Box {
|
Box {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.combinedClickable(
|
.clickable(onClick = onClick),
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = { if (!isDragging) showMenu = true },
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
|
Box {
|
||||||
Canvas(
|
Canvas(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.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(
|
DropdownMenu(
|
||||||
expanded = showMenu,
|
expanded = showMenu,
|
||||||
@@ -266,20 +272,17 @@ private fun PageThumbnail(
|
|||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Export as JPG") },
|
text = { Text("Export as JPG") },
|
||||||
onClick = {
|
onClick = { showMenu = false; onExportJpg() },
|
||||||
showMenu = false
|
|
||||||
onExportJpg()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Delete page") },
|
text = { Text("Delete page") },
|
||||||
onClick = {
|
onClick = { showMenu = false; onDelete() },
|
||||||
showMenu = false
|
|
||||||
onDelete()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = "Page ${page.pageNumber}",
|
text = "Page ${page.pageNumber}",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ class PageListViewModel(
|
|||||||
fun deletePage(pageId: Long) {
|
fun deletePage(pageId: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
repository.deletePage(pageId)
|
repository.deletePage(pageId)
|
||||||
|
// Renumber remaining pages sequentially
|
||||||
|
val remaining = repository.getPagesList(notebookId)
|
||||||
|
repository.reorderPages(notebookId, remaining.map { it.id })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user