Feature: save PDF to device
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<!-- Required (on Android 9 and lower) to save files -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
||||
@@ -3,7 +3,9 @@ package org.mydomain.myscan
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaScannerConnection
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
@@ -23,6 +25,8 @@ import org.mydomain.myscan.view.CameraScreen
|
||||
import org.mydomain.myscan.view.PagePreviewScreen
|
||||
import org.opencv.android.OpenCVLoader
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@@ -48,7 +52,8 @@ class MainActivity : ComponentActivity() {
|
||||
image = screen.image,
|
||||
isProcessing = screen.isProcessing,
|
||||
onBackPressed = { viewModel.navigateTo(Screen.Camera) },
|
||||
onSavePressed = createPdfAndShare(context)
|
||||
onSavePressed = createPdfAndSave(context),
|
||||
onSharePressed = createPdfAndShare(context),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -61,7 +66,18 @@ class MainActivity : ComponentActivity() {
|
||||
private fun createPdfAndShare(context: Context): (Bitmap) -> Unit = { bitmap ->
|
||||
val outputDir = File(cacheDir, "pdfs").apply { mkdirs() }
|
||||
val outputFile = File(outputDir, "scan_${System.currentTimeMillis()}.pdf")
|
||||
val success = createPdfFromBitmaps(listOf(bitmap), outputFile)
|
||||
val document = createPdfFromBitmaps(listOf(bitmap))
|
||||
var success = true
|
||||
try {
|
||||
FileOutputStream(outputFile).use { outputStream ->
|
||||
document.writeTo(outputStream)
|
||||
}
|
||||
} catch (_: IOException) {
|
||||
Toast.makeText(context, "Failed to share PDF", Toast.LENGTH_SHORT).show()
|
||||
success = false
|
||||
} finally {
|
||||
document.close()
|
||||
}
|
||||
if (success) {
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
@@ -74,8 +90,30 @@ class MainActivity : ComponentActivity() {
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
startActivity(Intent.createChooser(shareIntent, "Share PDF"))
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
fun createPdfAndSave(context: Context): (Bitmap) -> Unit = { bitmap ->
|
||||
val document = createPdfFromBitmaps(listOf(bitmap))
|
||||
try {
|
||||
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
if (!downloadsDir.exists()) downloadsDir.mkdirs()
|
||||
val file = File(downloadsDir, "scan_${System.currentTimeMillis()}.pdf")
|
||||
val outputStream = FileOutputStream(file)
|
||||
document.writeTo(outputStream)
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
MediaScannerConnection.scanFile(
|
||||
context, arrayOf(file.absolutePath), arrayOf("application/pdf"), null
|
||||
)
|
||||
|
||||
Toast.makeText(context, "Saved PDF in Downloads", Toast.LENGTH_SHORT).show()
|
||||
} catch (e: Exception) {
|
||||
Log.e("MyScan", "Failed to save PDF", e)
|
||||
Toast.makeText(context, "Failed to save PDF", Toast.LENGTH_SHORT).show()
|
||||
} finally {
|
||||
document.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,37 +3,24 @@ package org.mydomain.myscan
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.pdf.PdfDocument
|
||||
import android.util.Log
|
||||
import androidx.core.graphics.scale
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import kotlin.math.max
|
||||
|
||||
fun createPdfFromBitmaps(bitmaps: List<Bitmap>, outputFile: File): Boolean {
|
||||
fun createPdfFromBitmaps (bitmaps: List<Bitmap>): PdfDocument {
|
||||
val document = PdfDocument()
|
||||
try {
|
||||
for ((index, bitmap) in bitmaps.map { resizeImage(it) }.withIndex()) {
|
||||
val jpegStream = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 72, jpegStream)
|
||||
val compressedBytes = jpegStream.toByteArray()
|
||||
val compressedBitmap = BitmapFactory.decodeByteArray(compressedBytes, 0, compressedBytes.size)
|
||||
val compressedBitmap =
|
||||
BitmapFactory.decodeByteArray(compressedBytes, 0, compressedBytes.size)
|
||||
val pageInfo = PdfDocument.PageInfo.Builder(bitmap.width, bitmap.height, index + 1).create()
|
||||
val page = document.startPage(pageInfo)
|
||||
page.canvas.drawBitmap(compressedBitmap, 0f, 0f, null)
|
||||
document.finishPage(page)
|
||||
}
|
||||
FileOutputStream(outputFile).use { outputStream ->
|
||||
document.writeTo(outputStream)
|
||||
}
|
||||
return true
|
||||
} catch (e: IOException) {
|
||||
Log.e("MyScan", "Error writing PDF: ${e.message}")
|
||||
return false
|
||||
} finally {
|
||||
document.close()
|
||||
}
|
||||
return document
|
||||
}
|
||||
|
||||
fun resizeImage(original: Bitmap): Bitmap {
|
||||
|
||||
@@ -3,8 +3,11 @@ package org.mydomain.myscan.view
|
||||
import android.graphics.Bitmap
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Button
|
||||
@@ -25,6 +28,7 @@ fun PagePreviewScreen(
|
||||
isProcessing: Boolean,
|
||||
onBackPressed: () -> Unit,
|
||||
onSavePressed: (Bitmap) -> Unit,
|
||||
onSharePressed: (Bitmap) -> Unit,
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
when {
|
||||
@@ -41,13 +45,18 @@ fun PagePreviewScreen(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
Button (
|
||||
onClick = { onSavePressed(image) },
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text("Save as PDF")
|
||||
Button (onClick = { onSavePressed(image) }) {
|
||||
Text("Save PDF")
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Button (onClick = { onSharePressed(image) }) {
|
||||
Text("Share PDF")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
||||
Reference in New Issue
Block a user