Merge: notebook zip backup export
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package net.metacircular.engpad.ui.export
|
||||
|
||||
import android.content.Context
|
||||
import net.metacircular.engpad.data.db.toFloatArray
|
||||
import net.metacircular.engpad.data.repository.NotebookRepository
|
||||
import net.metacircular.engpad.data.repository.PageRepository
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
object BackupExporter {
|
||||
|
||||
suspend fun exportNotebook(
|
||||
context: Context,
|
||||
notebookId: Long,
|
||||
notebookRepository: NotebookRepository,
|
||||
pageRepository: PageRepository,
|
||||
): File {
|
||||
val notebook = notebookRepository.getById(notebookId)
|
||||
?: throw IllegalArgumentException("Notebook $notebookId not found")
|
||||
|
||||
val pages = pageRepository.getPagesList(notebookId)
|
||||
.sortedBy { it.pageNumber }
|
||||
|
||||
val exportDir = File(context.cacheDir, "exports")
|
||||
exportDir.mkdirs()
|
||||
val sanitizedTitle = PdfExporter.sanitize(notebook.title)
|
||||
val file = File(exportDir, "$sanitizedTitle.engpad.zip")
|
||||
|
||||
ZipOutputStream(file.outputStream()).use { zip ->
|
||||
val notebookJson = JSONObject().apply {
|
||||
put("title", notebook.title)
|
||||
put("page_size", notebook.pageSize)
|
||||
put("page_count", pages.size)
|
||||
}
|
||||
zip.putNextEntry(ZipEntry("notebook.json"))
|
||||
zip.write(notebookJson.toString(2).toByteArray())
|
||||
zip.closeEntry()
|
||||
|
||||
for (page in pages) {
|
||||
val strokes = pageRepository.getStrokes(page.id)
|
||||
val strokesJson = JSONArray()
|
||||
for (stroke in strokes) {
|
||||
val points = stroke.pointData.toFloatArray()
|
||||
val pointsArray = JSONArray()
|
||||
for (p in points) {
|
||||
pointsArray.put(p.toDouble())
|
||||
}
|
||||
strokesJson.put(JSONObject().apply {
|
||||
put("pen_size", stroke.penSize.toDouble())
|
||||
put("color", stroke.color)
|
||||
put("style", stroke.style)
|
||||
put("points", pointsArray)
|
||||
})
|
||||
}
|
||||
val pageJson = JSONObject().apply {
|
||||
put("page_number", page.pageNumber)
|
||||
put("strokes", strokesJson)
|
||||
}
|
||||
val entryName = "pages/%03d.json".format(page.pageNumber)
|
||||
zip.putNextEntry(ZipEntry(entryName))
|
||||
zip.write(pageJson.toString(2).toByteArray())
|
||||
zip.closeEntry()
|
||||
}
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,6 @@ object PdfExporter {
|
||||
}
|
||||
}
|
||||
|
||||
private fun sanitize(name: String): String =
|
||||
internal fun sanitize(name: String): String =
|
||||
name.replace(Regex("[^a-zA-Z0-9._-]"), "_")
|
||||
}
|
||||
|
||||
@@ -31,16 +31,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.platform.LocalContext
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.metacircular.engpad.data.db.EngPadDatabase
|
||||
import net.metacircular.engpad.data.model.Notebook
|
||||
import net.metacircular.engpad.data.model.PageSize
|
||||
import net.metacircular.engpad.data.repository.NotebookRepository
|
||||
import net.metacircular.engpad.data.repository.PageRepository
|
||||
import net.metacircular.engpad.ui.export.BackupExporter
|
||||
import net.metacircular.engpad.ui.export.PdfExporter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
@@ -62,6 +70,9 @@ fun NotebookListScreen(
|
||||
val repository = remember {
|
||||
NotebookRepository(database.notebookDao(), database.pageDao())
|
||||
}
|
||||
val pageRepository = remember {
|
||||
PageRepository(database.pageDao(), database.strokeDao())
|
||||
}
|
||||
val viewModel: NotebookListViewModel = viewModel(
|
||||
factory = NotebookListViewModel.Factory(repository),
|
||||
)
|
||||
@@ -69,6 +80,8 @@ fun NotebookListScreen(
|
||||
var showCreateDialog by remember { mutableStateOf(false) }
|
||||
var notebookToDelete by remember { mutableStateOf<Notebook?>(null) }
|
||||
var notebookToRename by remember { mutableStateOf<Notebook?>(null) }
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var filterText by remember { mutableStateOf("") }
|
||||
var sortField by remember { mutableStateOf(SortField.LAST_EDITED) }
|
||||
@@ -171,6 +184,16 @@ fun NotebookListScreen(
|
||||
onClick = { onNotebookClick(notebook.id) },
|
||||
onRename = { notebookToRename = notebook },
|
||||
onDelete = { notebookToDelete = notebook },
|
||||
onExportBackup = {
|
||||
scope.launch {
|
||||
val file = withContext(Dispatchers.IO) {
|
||||
BackupExporter.exportNotebook(
|
||||
context, notebook.id, repository, pageRepository,
|
||||
)
|
||||
}
|
||||
PdfExporter.shareFile(context, file, "application/zip")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -217,6 +240,7 @@ private fun NotebookItem(
|
||||
onClick: () -> Unit,
|
||||
onRename: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
onExportBackup: () -> Unit,
|
||||
) {
|
||||
val dateFormat = remember { SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US) }
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
@@ -262,6 +286,10 @@ private fun NotebookItem(
|
||||
text = { Text("Rename") },
|
||||
onClick = { showMenu = false; onRename() },
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Export backup") },
|
||||
onClick = { showMenu = false; onExportBackup() },
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("Delete") },
|
||||
onClick = { showMenu = false; onDelete() },
|
||||
|
||||
Reference in New Issue
Block a user