diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a26d297..c645e1e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -43,7 +43,7 @@
android:theme="@style/Theme.HPOSTesting"
tools:targetApi="31">
) {
+ if (dataList.isEmpty()) {
+ Toast.makeText(context, "No items selected for report generation", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ Log.d("ReportGen", "Generating report for ${dataList.size} items")
+
+ val message = if (dataList.size == 1) {
+ "Report for kit ${dataList.first()._id} started."
+ } else {
+ "Report for ${dataList.size} kits started."
+ }
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
+
+ val pdfFile = createProfessionalPdfReport(context, dataList)
+ previewPdf(context, pdfFile)
+ }
+
+ private fun createProfessionalPdfReport(context: Context, dataList: List): File {
+ val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
+ val file = File(context.getExternalFilesDir(null), "HemoCube_Report_$timestamp.pdf")
+
+ val pdfDocument = PdfDocument()
+
+ // Paint styles
+ val headerPaint = Paint().apply {
+ typeface = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD)
+ textSize = 20f
+ color = Color.rgb(0, 102, 153) // Professional blue
+ }
+
+ val titlePaint = Paint().apply {
+ typeface = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD)
+ textSize = 18f
+ color = Color.BLACK
+ }
+
+ val sectionHeaderPaint = Paint().apply {
+ typeface = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD)
+ textSize = 14f
+ color = Color.rgb(0, 102, 153)
+ }
+
+ val contentPaint = Paint().apply {
+ textSize = 11f
+ color = Color.DKGRAY
+ }
+
+ val boldContentPaint = Paint().apply {
+ typeface = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD)
+ textSize = 11f
+ color = Color.BLACK
+ }
+
+ val disclaimerPaint = Paint().apply {
+ textSize = 9f
+ color = Color.GRAY
+ typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)
+ }
+
+ // Create one page per test data
+ dataList.forEachIndexed { index, testData ->
+ val pageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, PAGE_HEIGHT, index + 1).create()
+ val page = pdfDocument.startPage(pageInfo)
+ val canvas = page.canvas
+
+ drawPage(canvas, testData, index + 1, dataList.size,
+ headerPaint, titlePaint, sectionHeaderPaint,
+ contentPaint, boldContentPaint, disclaimerPaint,context)
+
+ pdfDocument.finishPage(page)
+ }
+
+ pdfDocument.writeTo(FileOutputStream(file))
+ pdfDocument.close()
+ return file
+ }
+
+ private fun drawPage(
+ canvas: Canvas,
+ testData: HemoCubeTestData,
+ pageNumber: Int,
+ totalPages: Int,
+ headerPaint: Paint,
+ titlePaint: Paint,
+ sectionHeaderPaint: Paint,
+ contentPaint: Paint,
+ boldContentPaint: Paint,
+ disclaimerPaint: Paint,
+ context: Context
+ ) {
+ var currentY = MARGIN_TOP
+
+ // Header with logo placeholder and title
+ drawHeader(canvas, headerPaint, titlePaint, currentY, context)
+ currentY += 100f
+
+ // Sample Information Section
+ currentY = drawSection(canvas, "SAMPLE INFORMATION", sectionHeaderPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Sample ID:", testData.sampleid.toString(), boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Blood Group:", testData.bloodGroup, boldContentPaint, contentPaint, currentY)
+ currentY += 15f
+
+ // Test Information Section
+ currentY = drawSection(canvas, "TEST INFORMATION", sectionHeaderPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Test ID:", testData._id, boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Test Type:", testData.testType ?: "TrueHeme", boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Test Time:", testData.testTime ?: "N/A", boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Incubation Time:", testData.incubationTime, boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Kit Serial:", testData.kitSerial, boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Device Serial:", testData.deviceSerialNumber, boldContentPaint, contentPaint, currentY)
+ currentY += 15f
+
+ // Test Results Section
+ currentY = drawSection(canvas, "TEST RESULTS", sectionHeaderPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Classification Result:", testData.classificationResult, boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Device Ratio:", testData.deviceRatio?.toString() ?: "N/A", boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Calculated Ratio:", testData.calculatedRatio?.toString() ?: "N/A", boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Slope Ratio:", testData.slopeRatio?.toString() ?: "N/A", boldContentPaint, contentPaint, currentY)
+ currentY += 15f
+
+ // LED Measurements Section
+ currentY = drawSection(canvas, "LED MEASUREMENTS", sectionHeaderPaint, currentY)
+ currentY = drawLEDData(canvas, testData, boldContentPaint, contentPaint, currentY)
+ currentY += 15f
+
+ // Laboratory Information Section
+ currentY = drawSection(canvas, "LABORATORY INFORMATION", sectionHeaderPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Lab Name:", testData.labName ?: "N/A", boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "Center Name:", testData.centerName ?: "N/A", boldContentPaint, contentPaint, currentY)
+ currentY = drawKeyValuePair(canvas, "District:", testData.district ?: "N/A", boldContentPaint, contentPaint, currentY)
+ currentY += 15f
+
+ // Footer with page number and disclaimer
+ drawFooter(canvas, pageNumber, totalPages, contentPaint, disclaimerPaint)
+ }
+
+ private fun drawHeader(canvas: Canvas, headerPaint: Paint, titlePaint: Paint, startY: Float, context: Context) {
+ // Load and draw actual logo
+ try {
+ // Method 1: Load from drawable resources
+ val logoDrawable = ContextCompat.getDrawable(context, R.mipmap.hpos_icon) // Replace with your logo resource
+ logoDrawable?.let { drawable ->
+ val logoWidth = 70
+ val logoHeight = 60
+ drawable.setBounds(
+ MARGIN_LEFT.toInt(),
+ (startY - 20f).toInt(),
+ (MARGIN_LEFT + logoWidth).toInt(),
+ (startY + logoHeight - 20f).toInt()
+ )
+ drawable.draw(canvas)
+ }
+ } catch (e: Exception) {
+ // Fallback to placeholder if logo loading fails
+ val logoPaint = Paint().apply {
+ color = Color.rgb(0, 102, 153)
+ style = Paint.Style.FILL
+ }
+ canvas.drawRect(MARGIN_LEFT, startY - 20f, MARGIN_LEFT + 60f, startY + 20f, logoPaint)
+
+ // Draw "LOGO" text as placeholder
+ val placeholderPaint = Paint().apply {
+ color = Color.WHITE
+ textSize = 12f
+ typeface = Typeface.create(Typeface.DEFAULT_BOLD, Typeface.BOLD)
+ textAlign = Paint.Align.CENTER
+ }
+ canvas.drawText("LOGO", MARGIN_LEFT + 30f, startY, placeholderPaint)
+ }
+
+ // Company name and title
+ canvas.drawText("HemoCube", MARGIN_LEFT + 80f, startY + 10f, headerPaint)
+ canvas.drawText("Medical Test Report", MARGIN_LEFT, startY + 40f, titlePaint)
+
+ // Report generation date
+ val currentDate = SimpleDateFormat("dd MMM yyyy, HH:mm", Locale.getDefault()).format(Date())
+ canvas.drawText("Generated: $currentDate", PAGE_WIDTH - MARGIN_RIGHT - 150f, startY + 10f, Paint().apply {
+ textSize = 10f
+ color = Color.GRAY
+ })
+
+ // Line under header
+ val linePaint = Paint().apply {
+ color = Color.rgb(0, 102, 153)
+ strokeWidth = 2f
+ }
+ canvas.drawLine(MARGIN_LEFT, startY + 60f, PAGE_WIDTH - MARGIN_RIGHT, startY + 60f, linePaint)
+ }
+
+ private fun drawSection(canvas: Canvas, title: String, paint: Paint, y: Float): Float {
+ canvas.drawText(title, MARGIN_LEFT, y, paint)
+
+ // Underline for section
+ val linePaint = Paint().apply {
+ color = Color.rgb(0, 102, 153)
+ strokeWidth = 1f
+ }
+ canvas.drawLine(MARGIN_LEFT, y + 5f, MARGIN_LEFT + paint.measureText(title), y + 5f, linePaint)
+
+ return y + 25f
+ }
+
+ private fun drawKeyValuePair(canvas: Canvas, key: String, value: String, keyPaint: Paint, valuePaint: Paint, y: Float): Float {
+ canvas.drawText(key, MARGIN_LEFT + 10f, y, keyPaint)
+ canvas.drawText(value, MARGIN_LEFT + 150f, y, valuePaint)
+ return y + 18f
+ }
+
+ private fun drawLEDData(canvas: Canvas, testData: HemoCubeTestData, keyPaint: Paint, valuePaint: Paint, startY: Float): Float {
+ var currentY = startY
+
+ currentY = drawKeyValuePair(canvas, "LED1 Sample:", testData.led1Sample?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
+ currentY = drawKeyValuePair(canvas, "LED2 Sample:", testData.led2Sample?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
+ currentY = drawKeyValuePair(canvas, "LED3 Sample:", testData.led3Sample?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
+ currentY = drawKeyValuePair(canvas, "LED4 Sample:", testData.led4Sample?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
+
+ currentY = drawKeyValuePair(canvas, "ABS1:", testData.abs1?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
+ currentY = drawKeyValuePair(canvas, "ABS2:", testData.abs2?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
+ currentY = drawKeyValuePair(canvas, "ABS3:", testData.abs3?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
+ currentY = drawKeyValuePair(canvas, "ABS4:", testData.abs4?.toString() ?: "N/A", keyPaint, valuePaint, currentY)
+
+ return currentY
+ }
+
+ private fun drawFooter(canvas: Canvas, pageNumber: Int, totalPages: Int, contentPaint: Paint, disclaimerPaint: Paint) {
+ val footerY = PAGE_HEIGHT - MARGIN_BOTTOM + 20f
+
+ // Page number
+ canvas.drawText("Page $pageNumber of $totalPages", PAGE_WIDTH - MARGIN_RIGHT - 80f, footerY, contentPaint)
+
+ // Disclaimer
+ val disclaimerText = "DISCLAIMER: This report is generated by HemoCube automated system. " +
+ "Results should be interpreted by qualified medical professionals. " +
+ "This report is for diagnostic purposes only and should not be used as the sole basis for medical decisions. " +
+ "Please consult with your healthcare provider for proper interpretation and treatment recommendations."
+
+ val disclaimerLines = wrapText(disclaimerText, disclaimerPaint, PAGE_WIDTH - MARGIN_LEFT - MARGIN_RIGHT)
+ var disclaimerY = footerY + 20f
+
+ disclaimerLines.forEach { line ->
+ canvas.drawText(line, MARGIN_LEFT, disclaimerY, disclaimerPaint)
+ disclaimerY += 12f
+ }
+
+ // Footer line
+ val linePaint = Paint().apply {
+ color = Color.rgb(0, 102, 153)
+ strokeWidth = 1f
+ }
+ canvas.drawLine(MARGIN_LEFT, footerY - 10f, PAGE_WIDTH - MARGIN_RIGHT, footerY - 10f, linePaint)
+ }
+
+ private fun wrapText(text: String, paint: Paint, maxWidth: Float): List {
+ val words = text.split(" ")
+ val lines = mutableListOf()
+ var currentLine = ""
+
+ for (word in words) {
+ val testLine = if (currentLine.isEmpty()) word else "$currentLine $word"
+ if (paint.measureText(testLine) <= maxWidth) {
+ currentLine = testLine
+ } else {
+ if (currentLine.isNotEmpty()) {
+ lines.add(currentLine)
+ }
+ currentLine = word
+ }
+ }
+
+ if (currentLine.isNotEmpty()) {
+ lines.add(currentLine)
+ }
+
+ return lines
+ }
+
+ private fun previewPdf(context: Context, file: File) {
+ val uri = FileProvider.getUriForFile(
+ context,
+ "${context.packageName}.fileprovider",
+ file
+ )
+
+ val intent = Intent(Intent.ACTION_VIEW).apply {
+ setDataAndType(uri, "application/pdf")
+ flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NO_HISTORY
+ }
+
+ try {
+ context.startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ Toast.makeText(context, "No PDF viewer found", Toast.LENGTH_SHORT).show()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/hpostesting/data/model/reportgen/ui/report/ReportFragment.kt b/app/src/main/java/com/example/hpostesting/presentation/reportgen/ui/report/ReportFragment.kt
similarity index 87%
rename from app/src/main/java/com/example/hpostesting/data/model/reportgen/ui/report/ReportFragment.kt
rename to app/src/main/java/com/example/hpostesting/presentation/reportgen/ui/report/ReportFragment.kt
index 623d01e..8ba89bd 100644
--- a/app/src/main/java/com/example/hpostesting/data/model/reportgen/ui/report/ReportFragment.kt
+++ b/app/src/main/java/com/example/hpostesting/presentation/reportgen/ui/report/ReportFragment.kt
@@ -1,4 +1,4 @@
-package com.example.hpostesting.data.model.reportgen.ui.report
+package com.example.hpostesting.presentation.reportgen.ui.report
import android.app.DatePickerDialog
import android.content.Context
@@ -204,31 +204,3 @@ class ReportFragment : Fragment() {
updateUI()
}
}
-
-
-
-object ReportGen {
- fun generate(context: Context, dataList: List) {
- if (dataList.isEmpty()) {
- Toast.makeText(context, "No items selected for report generation", Toast.LENGTH_SHORT).show()
- return
- }
-
- Log.d("ReportGen", "Generating report for ${dataList.size} items")
-
- val kitNumbers = dataList.map { it._id }
- val message = if (dataList.size == 1) {
- "Report for kit ${kitNumbers.first()} started."
- } else {
- "Report for ${dataList.size} kits started."
- }
-
- Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
-
- // TODO: Add PDF generation, email, or preview logic here
- // You can iterate through dataList to include all selected items in the report
- dataList.forEach { item->
- Log.d("ReportGen", "Processing kit: ${item._id}, Result: ${item.classificationResult}, Time: ${item.testTime}")
- }
- }
-}
diff --git a/app/src/main/java/com/example/hpostesting/data/model/reportgen/ui/report/ReportViewModel.kt b/app/src/main/java/com/example/hpostesting/presentation/reportgen/ui/report/ReportViewModel.kt
similarity index 90%
rename from app/src/main/java/com/example/hpostesting/data/model/reportgen/ui/report/ReportViewModel.kt
rename to app/src/main/java/com/example/hpostesting/presentation/reportgen/ui/report/ReportViewModel.kt
index 925fa78..77fb20d 100644
--- a/app/src/main/java/com/example/hpostesting/data/model/reportgen/ui/report/ReportViewModel.kt
+++ b/app/src/main/java/com/example/hpostesting/presentation/reportgen/ui/report/ReportViewModel.kt
@@ -1,4 +1,4 @@
-package com.example.hpostesting.data.model.reportgen.ui.report
+package com.example.hpostesting.presentation.reportgen.ui.report
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel