Add page long-press menu (delete/export JPG) in view all pages
- Long-press a page thumbnail to show context menu with Delete and Export as JPG options - Delete shows confirmation dialog - Export renders page at 300 DPI and shares via intent - FAB always creates new pages (no empty-page restriction in page list) - Note: drag-to-reorder deferred — requires custom grid drag handling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
package net.metacircular.engpad.ui.pages
|
||||
|
||||
import android.graphics.Color as AndroidColor
|
||||
import android.graphics.Paint as AndroidPaint
|
||||
import android.graphics.Path as AndroidPath
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.clickable
|
||||
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.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -14,12 +15,16 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
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
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -27,20 +32,24 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||
import androidx.compose.ui.graphics.nativeCanvas
|
||||
import androidx.core.graphics.withScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.withScale
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import net.metacircular.engpad.data.db.EngPadDatabase
|
||||
import net.metacircular.engpad.data.db.toFloatArray
|
||||
import net.metacircular.engpad.data.model.Page
|
||||
import net.metacircular.engpad.data.model.PageSize
|
||||
import net.metacircular.engpad.data.model.Stroke
|
||||
import net.metacircular.engpad.data.repository.PageRepository
|
||||
import net.metacircular.engpad.ui.export.PdfExporter
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -59,6 +68,10 @@ fun PageListScreen(
|
||||
)
|
||||
val pages by viewModel.pages.collectAsState()
|
||||
val aspectRatio = pageSize.widthPt.toFloat() / pageSize.heightPt.toFloat()
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var pageToDelete by remember { mutableStateOf<Page?>(null) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -97,13 +110,45 @@ fun PageListScreen(
|
||||
aspectRatio = aspectRatio,
|
||||
repository = repository,
|
||||
onClick = { onPageClick(page.id, pageSize) },
|
||||
onDelete = { pageToDelete = page },
|
||||
onExportJpg = {
|
||||
scope.launch {
|
||||
val strokes = viewModel.getStrokes(page.id)
|
||||
val file = PdfExporter.exportPageAsJpg(
|
||||
context = context,
|
||||
notebookTitle = notebookTitle,
|
||||
pageSize = pageSize,
|
||||
pageNumber = page.pageNumber,
|
||||
strokes = strokes,
|
||||
)
|
||||
PdfExporter.shareFile(context, file, "image/jpeg")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pageToDelete?.let { page ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { pageToDelete = null },
|
||||
title = { Text("Delete Page") },
|
||||
text = { Text("Delete page ${page.pageNumber}? This cannot be undone.") },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
viewModel.deletePage(page.id)
|
||||
pageToDelete = null
|
||||
}) { Text("Delete") }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { pageToDelete = null }) { Text("Cancel") }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun PageThumbnail(
|
||||
page: Page,
|
||||
@@ -111,8 +156,11 @@ private fun PageThumbnail(
|
||||
aspectRatio: Float,
|
||||
repository: PageRepository,
|
||||
onClick: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
onExportJpg: () -> Unit,
|
||||
) {
|
||||
var strokes by remember(page.id) { mutableStateOf<List<Stroke>>(emptyList()) }
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(page.id) {
|
||||
strokes = repository.getStrokes(page.id)
|
||||
@@ -120,10 +168,15 @@ private fun PageThumbnail(
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.clickable(onClick = onClick),
|
||||
) {
|
||||
Box {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = { showMenu = true },
|
||||
),
|
||||
) {
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
@@ -161,6 +214,26 @@ private fun PageThumbnail(
|
||||
}
|
||||
}
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false },
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Export as JPG") },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onExportJpg()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Delete page") },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
onDelete()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = "Page ${page.pageNumber}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import net.metacircular.engpad.data.model.Page
|
||||
import net.metacircular.engpad.data.model.Stroke
|
||||
import net.metacircular.engpad.data.repository.PageRepository
|
||||
|
||||
class PageListViewModel(
|
||||
@@ -24,6 +25,15 @@ class PageListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun deletePage(pageId: Long) {
|
||||
viewModelScope.launch {
|
||||
repository.deletePage(pageId)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getStrokes(pageId: Long): List<Stroke> =
|
||||
repository.getStrokes(pageId)
|
||||
|
||||
class Factory(
|
||||
private val notebookId: Long,
|
||||
private val repository: PageRepository,
|
||||
|
||||
Reference in New Issue
Block a user