david dereba
02/18/2025, 12:34 PMclass PDFGenerator(private val context: Context) {
private var webView: WebView? = null
private val templateEngine: TemplateEngine by lazy {
TemplateEngine().apply {
setTemplateResolver(AssetTemplateResolver(context, "templates/", ".html"))
}
}
sealed class PdfResult {
data object Success : PdfResult()
data class Error(val message: String) : PdfResult()
}
suspend fun generatePDF(
customerName: String,
amount: String,
date: String,
onProgress: (Float) -> Unit
): PdfResult = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
try {
// Format amount with currency
val formattedAmount = try {
val amountValue = amount.toDoubleOrNull() ?: 0.0
String.format("$%,.2f", amountValue)
} catch (e: Exception) {
"$${amount}"
}
// Format date to be more readable
val formattedDate = try {
val parsedDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(date)
SimpleDateFormat("MMMM dd, yyyy", Locale.getDefault()).format(parsedDate!!)
} catch (e: Exception) {
date
}
val data = mapOf(
"customerName" to customerName,
"amount" to formattedAmount,
"date" to formattedDate
)
val htmlContent = processTemplate(data)
val result = generateAndSavePdf(htmlContent, onProgress)
cleanup()
result
} catch (e: Exception) {
Log.e(TAG, "PDF generation failed", e)
PdfResult.Error(e.message ?: "Unknown error occurred")
}
}
private fun processTemplate(data: Map<String, String>): String {
val thymeleafContext = ThymeleafContext()
data.forEach { (key, value) -> thymeleafContext.setVariable(key, value) }
return templateEngine.process("invoice_template", thymeleafContext)
}
private suspend fun generateAndSavePdf(
htmlContent: String,
onProgress: (Float) -> Unit
): PdfResult = withContext(Dispatchers.Main) {
try {
if (webView == null) {
webView = WebView(context).apply {
settings.apply {
javaScriptEnabled = false
defaultFontSize = 16
defaultTextEncodingName = "UTF-8"
}
}
}
val fileName = "invoice_${SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())}.pdf"
saveWithMediaStore(fileName, htmlContent, onProgress)
} catch (e: Exception) {
Log.e(TAG, "Failed to generate PDF", e)
PdfResult.Error("Failed to generate PDF: ${e.message}")
}
}
private suspend fun saveWithMediaStore(
fileName: String,
htmlContent: String,
onProgress: (Float) -> Unit
): PdfResult = withContext(Dispatchers.Main) {
var contentValues: ContentValues? = null
var uri: Uri? = null
try {
// Create ContentValues on IO thread
contentValues = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS)
put(MediaStore.MediaColumns.IS_PENDING, 1)
}
}
}
// Get URI on IO thread
uri = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
context.contentResolver.insert(
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),
contentValues
)
} else {
val documentsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
if (!documentsDir.exists()) {
documentsDir.mkdirs()
}
val file = File(documentsDir, fileName)
Uri.fromFile(file)
}
} ?: throw IOException("Failed to create new MediaStore record.")
// Initialize WebView if needed
if (webView == null) {
webView = WebView(context).apply {
settings.apply {
javaScriptEnabled = false
defaultFontSize = 16
defaultTextEncodingName = "UTF-8"
}
}
}
// Create a deferred to handle the async completion
val completableDeferred = CompletableDeferred<PdfResult>()
webView?.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
try {
val printAdapter = webView?.createPrintDocumentAdapter(fileName)
val printManager = context.getSystemService(Context.PRINT_SERVICE) as PrintManager
printManager.print(
fileName,
printAdapter!!,
PrintAttributes.Builder()
.setMediaSize(PrintAttributes.MediaSize.ISO_A4)
.setResolution(PrintAttributes.Resolution("pdf", "pdf", 600, 600))
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setMinMargins(PrintAttributes.Margins.NO_MARGINS)
.build()
)
completableDeferred.complete(PdfResult.Success)
} catch (e: Exception) {
completableDeferred.complete(PdfResult.Error("Failed to generate PDF: ${e.message}"))
}
}
}
// Load the content
webView?.loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null)
// Wait for the result
val result = completableDeferred.await()
// Update the MediaStore on IO thread
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues?.clear()
contentValues?.put(MediaStore.MediaColumns.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}
}
result
} catch (e: Exception) {
Log.e(TAG, "Failed to save PDF with MediaStore", e)
PdfResult.Error("Failed to save PDF: ${e.message}")
}
}
private fun cleanup() {
webView?.destroy()
webView = null
}
companion object {
private const val TAG = "PDFGenerator"
}
}
What could be the issue?Skaldebane
02/18/2025, 1:22 PMwithContext(Dispatchers.Main) { ... }
.