Updated the pdf generator, and improved pdf design
This commit is contained in:
@@ -1,19 +1,23 @@
|
|||||||
package `in`.sminnovations.smiadmin.ui
|
package `in`.sminnovations.smiadmin.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.LinearGradient
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Path
|
||||||
|
import android.graphics.Shader
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.graphics.pdf.PdfDocument
|
import android.graphics.pdf.PdfDocument
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.graphics.Color
|
|
||||||
import com.google.firebase.Firebase
|
import com.google.firebase.Firebase
|
||||||
import com.google.firebase.firestore.firestore
|
import com.google.firebase.firestore.firestore
|
||||||
import `in`.sminnovations.smiadmin.data.Employee
|
import `in`.sminnovations.smiadmin.data.Employee
|
||||||
import kotlinx.coroutines.tasks.await
|
import kotlinx.coroutines.tasks.await
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class PDFGenerator(
|
class PDFGenerator(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@@ -23,16 +27,22 @@ class PDFGenerator(
|
|||||||
private lateinit var currentPage: PdfDocument.Page
|
private lateinit var currentPage: PdfDocument.Page
|
||||||
private lateinit var canvas: android.graphics.Canvas
|
private lateinit var canvas: android.graphics.Canvas
|
||||||
private var currentY: Float = 0f
|
private var currentY: Float = 0f
|
||||||
private val startX = 40f
|
|
||||||
private val pageHeight = 842f // A4 height
|
|
||||||
private val pageWidth = 595f // A4 width
|
|
||||||
private var pageNumber = 1
|
|
||||||
|
|
||||||
// Define colors
|
// Constants for page layout
|
||||||
private val primaryColor = Color.rgb(41, 128, 185) // Blue
|
private val pageWidth = 595f // A4 width
|
||||||
private val secondaryColor = Color.rgb(44, 62, 80) // Dark Blue
|
private val pageHeight = 842f // A4 height
|
||||||
private val borderColor = Color.rgb(189, 195, 199) // Light Gray
|
private val margin = 40f
|
||||||
private val backgroundColor = Color.rgb(236, 240, 241) // Very Light Gray
|
private val contentWidth = pageWidth - (2 * margin)
|
||||||
|
|
||||||
|
// Professional color scheme
|
||||||
|
private val primaryColor = Color.rgb(41, 128, 185) // Professional blue
|
||||||
|
private val secondaryColor = Color.rgb(44, 62, 80) // Dark blue
|
||||||
|
private val accentColor = Color.rgb(230, 126, 34) // Orange for highlights
|
||||||
|
private val backgroundColor = Color.rgb(236, 240, 241) // Light gray
|
||||||
|
private val borderColor = Color.rgb(189, 195, 199) // Medium gray
|
||||||
|
private val textColor = Color.rgb(52, 73, 94) // Dark gray for text
|
||||||
|
|
||||||
|
private var pageNumber = 1
|
||||||
|
|
||||||
suspend fun generateAttendanceReport(
|
suspend fun generateAttendanceReport(
|
||||||
employees: List<Employee>,
|
employees: List<Employee>,
|
||||||
@@ -42,35 +52,43 @@ class PDFGenerator(
|
|||||||
document = PdfDocument()
|
document = PdfDocument()
|
||||||
createNewPage()
|
createNewPage()
|
||||||
|
|
||||||
// Setup paints
|
// Create paint objects
|
||||||
val headerPaint = Paint().apply {
|
val titlePaint = Paint().apply {
|
||||||
textSize = 28f
|
textSize = 32f
|
||||||
textAlign = Paint.Align.CENTER
|
typeface = Typeface.create("sans-serif-light", Typeface.BOLD)
|
||||||
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
|
||||||
color = primaryColor
|
color = primaryColor
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
}
|
}
|
||||||
|
|
||||||
val subHeaderPaint = Paint().apply {
|
val subtitlePaint = Paint().apply {
|
||||||
textSize = 20f
|
textSize = 24f
|
||||||
textAlign = Paint.Align.CENTER
|
typeface = Typeface.create("sans-serif-light", Typeface.NORMAL)
|
||||||
color = secondaryColor
|
color = secondaryColor
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
}
|
}
|
||||||
|
|
||||||
val tableHeaderPaint = Paint().apply {
|
val tableHeaderPaint = Paint().apply {
|
||||||
textSize = 14f
|
textSize = 14f
|
||||||
textAlign = Paint.Align.LEFT
|
typeface = Typeface.create("sans-serif-medium", Typeface.BOLD)
|
||||||
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
|
||||||
color = Color.WHITE
|
color = Color.WHITE
|
||||||
}
|
}
|
||||||
|
|
||||||
val tablePaint = Paint().apply {
|
val regularPaint = Paint().apply {
|
||||||
textSize = 12f
|
textSize = 12f
|
||||||
textAlign = Paint.Align.LEFT
|
typeface = Typeface.create("sans-serif", Typeface.NORMAL)
|
||||||
color = secondaryColor
|
color = textColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw headers and table
|
val highlightPaint = Paint().apply {
|
||||||
drawPageHeaders(headerPaint, subHeaderPaint, selectedYear, selectedMonth)
|
textSize = 12f
|
||||||
|
typeface = Typeface.create("sans-serif-medium", Typeface.BOLD)
|
||||||
|
color = accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw report header
|
||||||
|
drawReportHeader(selectedYear, selectedMonth, titlePaint, subtitlePaint)
|
||||||
|
|
||||||
|
// Draw table headers
|
||||||
drawTableHeaders(tableHeaderPaint)
|
drawTableHeaders(tableHeaderPaint)
|
||||||
|
|
||||||
// Process each employee
|
// Process each employee
|
||||||
@@ -78,7 +96,6 @@ class PDFGenerator(
|
|||||||
if (currentY > pageHeight - 100) {
|
if (currentY > pageHeight - 100) {
|
||||||
finishCurrentPage()
|
finishCurrentPage()
|
||||||
createNewPage()
|
createNewPage()
|
||||||
drawPageHeaders(headerPaint, subHeaderPaint, selectedYear, selectedMonth)
|
|
||||||
drawTableHeaders(tableHeaderPaint)
|
drawTableHeaders(tableHeaderPaint)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,58 +115,166 @@ class PDFGenerator(
|
|||||||
}
|
}
|
||||||
.sorted()
|
.sorted()
|
||||||
|
|
||||||
// Draw row background
|
drawEmployeeRow(employee, presentDates, regularPaint, highlightPaint)
|
||||||
val rowPaint = Paint().apply {
|
|
||||||
color = if ((employees.indexOf(employee) % 2) == 0) {
|
|
||||||
backgroundColor
|
|
||||||
} else {
|
|
||||||
Color.WHITE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val rowHeight = 60f
|
|
||||||
canvas.drawRect(startX, currentY, pageWidth - startX, currentY + rowHeight, rowPaint)
|
|
||||||
|
|
||||||
// Draw employee details
|
|
||||||
val nameY = currentY + 25f
|
|
||||||
canvas.drawText(employee.Name, startX + 10f, nameY, tablePaint)
|
|
||||||
canvas.drawText(
|
|
||||||
"${presentDates.size} days",
|
|
||||||
startX + 190f,
|
|
||||||
nameY,
|
|
||||||
tablePaint
|
|
||||||
)
|
|
||||||
|
|
||||||
// Format dates in groups of 5
|
|
||||||
val datesText = presentDates.chunked(5).joinToString("\n") { chunk ->
|
|
||||||
chunk.joinToString(", ") { day -> String.format("%02d", day) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw dates in multiple lines if needed
|
|
||||||
var dateY = nameY
|
|
||||||
datesText.split("\n").forEach { line ->
|
|
||||||
canvas.drawText(line, startX + 290f, dateY, tablePaint)
|
|
||||||
dateY += 15f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw row borders
|
|
||||||
val borderPaint = Paint().apply {
|
|
||||||
style = Paint.Style.STROKE
|
|
||||||
strokeWidth = 0.5f
|
|
||||||
color = borderColor
|
|
||||||
}
|
|
||||||
canvas.drawLine(
|
|
||||||
startX, currentY + rowHeight,
|
|
||||||
pageWidth - startX, currentY + rowHeight,
|
|
||||||
borderPaint
|
|
||||||
)
|
|
||||||
|
|
||||||
currentY += rowHeight
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add footer with page numbers
|
||||||
|
drawFooter(regularPaint)
|
||||||
|
|
||||||
// Finish and save document
|
// Finish and save document
|
||||||
finishCurrentPage()
|
finishCurrentPage()
|
||||||
|
return saveDocument(selectedYear, selectedMonth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawReportHeader(
|
||||||
|
selectedYear: Int,
|
||||||
|
selectedMonth: Int,
|
||||||
|
titlePaint: Paint,
|
||||||
|
subtitlePaint: Paint
|
||||||
|
) {
|
||||||
|
// Draw decorative header background
|
||||||
|
val headerPath = Path().apply {
|
||||||
|
moveTo(0f, 0f)
|
||||||
|
lineTo(pageWidth, 0f)
|
||||||
|
lineTo(pageWidth, 120f)
|
||||||
|
quadTo(pageWidth/2, 140f, 0f, 120f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawPath(headerPath, Paint().apply {
|
||||||
|
color = primaryColor
|
||||||
|
alpha = 20
|
||||||
|
})
|
||||||
|
|
||||||
|
// Draw company name
|
||||||
|
canvas.drawText(companyName, pageWidth/2, 70f, titlePaint)
|
||||||
|
|
||||||
|
// Draw report title
|
||||||
|
val monthName = SimpleDateFormat("MMMM", Locale.getDefault())
|
||||||
|
.format(Calendar.getInstance().apply {
|
||||||
|
set(Calendar.MONTH, selectedMonth - 1)
|
||||||
|
}.time)
|
||||||
|
|
||||||
|
canvas.drawText(
|
||||||
|
"Monthly Attendance Summary",
|
||||||
|
pageWidth/2,
|
||||||
|
105f,
|
||||||
|
subtitlePaint
|
||||||
|
)
|
||||||
|
|
||||||
|
canvas.drawText(
|
||||||
|
"$monthName $selectedYear",
|
||||||
|
pageWidth/2,
|
||||||
|
135f,
|
||||||
|
subtitlePaint
|
||||||
|
)
|
||||||
|
|
||||||
|
currentY = 180f
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawTableHeaders(headerPaint: Paint) {
|
||||||
|
val headerHeight = 45f
|
||||||
|
|
||||||
|
// Create gradient for header background
|
||||||
|
val gradient = LinearGradient(
|
||||||
|
margin, currentY,
|
||||||
|
margin, currentY + headerHeight,
|
||||||
|
primaryColor,
|
||||||
|
secondaryColor,
|
||||||
|
Shader.TileMode.CLAMP
|
||||||
|
)
|
||||||
|
|
||||||
|
// Draw header background with gradient
|
||||||
|
canvas.drawRoundRect(
|
||||||
|
margin, currentY,
|
||||||
|
pageWidth - margin,
|
||||||
|
currentY + headerHeight,
|
||||||
|
8f, 8f,
|
||||||
|
Paint().apply { shader = gradient }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Draw column headers
|
||||||
|
var xPos = margin + 15f
|
||||||
|
canvas.drawText("Employee Name", xPos, currentY + 30f, headerPaint)
|
||||||
|
|
||||||
|
xPos += 180f
|
||||||
|
canvas.drawText("Days Present", xPos, currentY + 30f, headerPaint)
|
||||||
|
|
||||||
|
xPos += 100f
|
||||||
|
canvas.drawText("Attendance Dates", xPos, currentY + 30f, headerPaint)
|
||||||
|
|
||||||
|
currentY += headerHeight + 5f
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawEmployeeRow(
|
||||||
|
employee: Employee,
|
||||||
|
presentDates: List<Int>,
|
||||||
|
regularPaint: Paint,
|
||||||
|
highlightPaint: Paint
|
||||||
|
) {
|
||||||
|
val rowHeight = 60f
|
||||||
|
|
||||||
|
// Draw row background
|
||||||
|
canvas.drawRoundRect(
|
||||||
|
margin, currentY,
|
||||||
|
pageWidth - margin,
|
||||||
|
currentY + rowHeight,
|
||||||
|
4f, 4f,
|
||||||
|
Paint().apply {
|
||||||
|
color = backgroundColor
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Draw employee name
|
||||||
|
canvas.drawText(
|
||||||
|
employee.Name,
|
||||||
|
margin + 15f,
|
||||||
|
currentY + 35f,
|
||||||
|
regularPaint
|
||||||
|
)
|
||||||
|
|
||||||
|
// Draw days present
|
||||||
|
canvas.drawText(
|
||||||
|
"${presentDates.size} days",
|
||||||
|
margin + 195f,
|
||||||
|
currentY + 35f,
|
||||||
|
highlightPaint
|
||||||
|
)
|
||||||
|
|
||||||
|
// Draw attendance dates
|
||||||
|
val datesText = presentDates.chunked(5)
|
||||||
|
.joinToString("\n") { chunk ->
|
||||||
|
chunk.joinToString(", ") {
|
||||||
|
String.format("%02d", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dateY = currentY + 25f
|
||||||
|
datesText.split("\n").forEach { line ->
|
||||||
|
canvas.drawText(
|
||||||
|
line,
|
||||||
|
margin + 295f,
|
||||||
|
dateY,
|
||||||
|
regularPaint
|
||||||
|
)
|
||||||
|
dateY += 15f
|
||||||
|
}
|
||||||
|
|
||||||
|
currentY += rowHeight + 5f
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawFooter(paint: Paint) {
|
||||||
|
paint.textAlign = Paint.Align.CENTER
|
||||||
|
paint.textSize = 10f
|
||||||
|
canvas.drawText(
|
||||||
|
"Page $pageNumber",
|
||||||
|
pageWidth/2,
|
||||||
|
pageHeight - margin/2,
|
||||||
|
paint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveDocument(selectedYear: Int, selectedMonth: Int): File {
|
||||||
val fileName = "Attendance_Summary_${selectedYear}_${selectedMonth}.pdf"
|
val fileName = "Attendance_Summary_${selectedYear}_${selectedMonth}.pdf"
|
||||||
val file = File(
|
val file = File(
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
@@ -165,58 +290,18 @@ class PDFGenerator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createNewPage() {
|
private fun createNewPage() {
|
||||||
val pageInfo = PdfDocument.PageInfo.Builder(pageWidth.toInt(), pageHeight.toInt(), pageNumber++).create()
|
val pageInfo = PdfDocument.PageInfo.Builder(
|
||||||
|
pageWidth.toInt(),
|
||||||
|
pageHeight.toInt(),
|
||||||
|
pageNumber++
|
||||||
|
).create()
|
||||||
|
|
||||||
currentPage = document.startPage(pageInfo)
|
currentPage = document.startPage(pageInfo)
|
||||||
canvas = currentPage.canvas
|
canvas = currentPage.canvas
|
||||||
currentY = 120f
|
currentY = margin
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun finishCurrentPage() {
|
private fun finishCurrentPage() {
|
||||||
document.finishPage(currentPage)
|
document.finishPage(currentPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawPageHeaders(
|
|
||||||
headerPaint: Paint,
|
|
||||||
subHeaderPaint: Paint,
|
|
||||||
selectedYear: Int,
|
|
||||||
selectedMonth: Int
|
|
||||||
) {
|
|
||||||
// Draw company logo background
|
|
||||||
val logoBgPaint = Paint().apply {
|
|
||||||
color = primaryColor
|
|
||||||
alpha = 30
|
|
||||||
}
|
|
||||||
canvas.drawCircle(pageWidth / 2f, 45f, 100f, logoBgPaint)
|
|
||||||
|
|
||||||
// Draw company name
|
|
||||||
canvas.drawText(companyName, pageWidth / 2f, 60f, headerPaint)
|
|
||||||
|
|
||||||
// Draw report title
|
|
||||||
val monthName = SimpleDateFormat("MMMM", Locale.getDefault())
|
|
||||||
.format(Calendar.getInstance().apply {
|
|
||||||
set(Calendar.MONTH, selectedMonth - 1)
|
|
||||||
}.time)
|
|
||||||
|
|
||||||
val reportTitle = "Monthly Attendance Summary - $monthName $selectedYear"
|
|
||||||
canvas.drawText(reportTitle, pageWidth / 2f, 95f, subHeaderPaint)
|
|
||||||
currentY = 140f
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun drawTableHeaders(headerPaint: Paint) {
|
|
||||||
// Draw header background
|
|
||||||
val headerBgPaint = Paint().apply {
|
|
||||||
color = primaryColor
|
|
||||||
}
|
|
||||||
|
|
||||||
val headerHeight = 40f
|
|
||||||
canvas.drawRect(startX, currentY, pageWidth - startX, currentY + headerHeight, headerBgPaint)
|
|
||||||
|
|
||||||
// Draw column headers
|
|
||||||
val textY = currentY + 25f
|
|
||||||
canvas.drawText("Employee Name", startX + 10f, textY, headerPaint)
|
|
||||||
canvas.drawText("Present Days", startX + 190f, textY, headerPaint)
|
|
||||||
canvas.drawText("Dates Present", startX + 290f, textY, headerPaint)
|
|
||||||
|
|
||||||
currentY += headerHeight
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user