generate pdf code added
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId "in.sminnovations.smiadmin"
|
applicationId "in.sminnovations.smiadmin"
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 1
|
versionCode 2
|
||||||
versionName "1.0"
|
versionName "1.1"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
package `in`.sminnovations.smiadmin.ui
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import com.google.firebase.Firebase
|
|
||||||
import com.google.firebase.firestore.firestore
|
|
||||||
import `in`.sminnovations.smiadmin.data.AttendanceData
|
|
||||||
import `in`.sminnovations.smiadmin.data.Employee
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.tasks.await
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class CSVGenerator(private val context: Context) {
|
|
||||||
suspend fun generateAttendanceReport(
|
|
||||||
employees: List<Employee>,
|
|
||||||
selectedYear: Int,
|
|
||||||
selectedMonth: Int
|
|
||||||
): File {
|
|
||||||
val fileName = "Attendance_${selectedYear}_${selectedMonth}.csv"
|
|
||||||
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
val contentValues = ContentValues().apply {
|
|
||||||
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
|
|
||||||
put(MediaStore.MediaColumns.MIME_TYPE, "text/csv")
|
|
||||||
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
|
||||||
}
|
|
||||||
|
|
||||||
val uri = context.contentResolver.insert(
|
|
||||||
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
|
|
||||||
contentValues
|
|
||||||
) ?: throw IOException("Failed to create new MediaStore record.")
|
|
||||||
|
|
||||||
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
|
|
||||||
val writer = outputStream.bufferedWriter()
|
|
||||||
|
|
||||||
val dates = mutableSetOf<String>()
|
|
||||||
val attendanceData = mutableMapOf<String, Map<String, AttendanceData>>()
|
|
||||||
|
|
||||||
// Format month with leading zero
|
|
||||||
val monthStr = String.format("%02d", selectedMonth)
|
|
||||||
|
|
||||||
// Collect attendance data for each employee
|
|
||||||
employees.forEach { employee ->
|
|
||||||
// Get all documents and filter by month pattern in document ID
|
|
||||||
val snapshot = Firebase.firestore
|
|
||||||
.collection("attendance_${employee.Id}")
|
|
||||||
.get()
|
|
||||||
.await()
|
|
||||||
|
|
||||||
val employeeAttendance = snapshot.documents
|
|
||||||
.filter { doc ->
|
|
||||||
// Filter documents that match the pattern DD-MM-YYYY for selected month
|
|
||||||
doc.id.matches(Regex("\\d{2}-$monthStr-$selectedYear"))
|
|
||||||
}
|
|
||||||
.associate { doc ->
|
|
||||||
dates.add(doc.id)
|
|
||||||
doc.id to AttendanceData(
|
|
||||||
date = doc.id,
|
|
||||||
checkIn = doc.getString("checkIn") ?: "N/A",
|
|
||||||
checkOut = doc.getString("checkOut") ?: "N/A"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
attendanceData[employee.Id] = employeeAttendance
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort dates
|
|
||||||
val sortedDates = dates.sortedBy { date ->
|
|
||||||
val parts = date.split("-")
|
|
||||||
val day = parts[0].toInt()
|
|
||||||
String.format("%02d", day)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write header
|
|
||||||
val headerRow = listOf("Employee ID", "Name") + sortedDates
|
|
||||||
writer.write(headerRow.joinToString(","))
|
|
||||||
writer.newLine()
|
|
||||||
|
|
||||||
// Write employee data
|
|
||||||
employees.forEach { employee ->
|
|
||||||
val rowData = mutableListOf<String>()
|
|
||||||
rowData.add(employee.Id)
|
|
||||||
rowData.add(employee.Name)
|
|
||||||
|
|
||||||
val employeeAttendance = attendanceData[employee.Id] ?: emptyMap()
|
|
||||||
sortedDates.forEach { date ->
|
|
||||||
val attendance = employeeAttendance[date]
|
|
||||||
val cellValue = when {
|
|
||||||
attendance == null -> "Absent"
|
|
||||||
attendance.checkOut == "N/A" -> attendance.checkIn
|
|
||||||
else -> "${attendance.checkIn} - ${attendance.checkOut}"
|
|
||||||
}
|
|
||||||
rowData.add("\"${cellValue.replace("\"", "\"\"")}\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.write(rowData.joinToString(","))
|
|
||||||
writer.newLine()
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
File(Environment.getExternalStorageDirectory(), "Download/$fileName")
|
|
||||||
} else {
|
|
||||||
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
|
||||||
val file = File(downloadsDir, fileName)
|
|
||||||
|
|
||||||
file.bufferedWriter().use { writer ->
|
|
||||||
val dates = mutableSetOf<String>()
|
|
||||||
val attendanceData = mutableMapOf<String, Map<String, AttendanceData>>()
|
|
||||||
|
|
||||||
val monthStr = String.format("%02d", selectedMonth)
|
|
||||||
|
|
||||||
employees.forEach { employee ->
|
|
||||||
val snapshot = Firebase.firestore
|
|
||||||
.collection("attendance_${employee.Id}")
|
|
||||||
.get()
|
|
||||||
.await()
|
|
||||||
|
|
||||||
val employeeAttendance = snapshot.documents
|
|
||||||
.filter { doc ->
|
|
||||||
doc.id.matches(Regex("\\d{2}-$monthStr-$selectedYear"))
|
|
||||||
}
|
|
||||||
.associate { doc ->
|
|
||||||
dates.add(doc.id)
|
|
||||||
doc.id to AttendanceData(
|
|
||||||
date = doc.id,
|
|
||||||
checkIn = doc.getString("checkIn") ?: "N/A",
|
|
||||||
checkOut = doc.getString("checkOut") ?: "N/A"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
attendanceData[employee.Id] = employeeAttendance
|
|
||||||
}
|
|
||||||
|
|
||||||
val sortedDates = dates.sortedBy { date ->
|
|
||||||
val parts = date.split("-")
|
|
||||||
val day = parts[0].toInt()
|
|
||||||
String.format("%02d", day)
|
|
||||||
}
|
|
||||||
|
|
||||||
val headerRow = listOf("Employee ID", "Name") + sortedDates
|
|
||||||
writer.write(headerRow.joinToString(","))
|
|
||||||
writer.newLine()
|
|
||||||
|
|
||||||
employees.forEach { employee ->
|
|
||||||
val rowData = mutableListOf<String>()
|
|
||||||
rowData.add(employee.Id)
|
|
||||||
rowData.add(employee.Name)
|
|
||||||
|
|
||||||
val employeeAttendance = attendanceData[employee.Id] ?: emptyMap()
|
|
||||||
sortedDates.forEach { date ->
|
|
||||||
val attendance = employeeAttendance[date]
|
|
||||||
val cellValue = when {
|
|
||||||
attendance == null -> "Absent"
|
|
||||||
attendance.checkOut == "N/A" -> attendance.checkIn
|
|
||||||
else -> "${attendance.checkIn} - ${attendance.checkOut}"
|
|
||||||
}
|
|
||||||
rowData.add("\"${cellValue.replace("\"", "\"\"")}\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.write(rowData.joinToString(","))
|
|
||||||
writer.newLine()
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
82
app/src/main/java/in/sminnovations/smiadmin/ui/ExportTask.kt
Normal file
82
app/src/main/java/in/sminnovations/smiadmin/ui/ExportTask.kt
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package `in`.sminnovations.smiadmin.ui
|
||||||
|
|
||||||
|
import android.app.ProgressDialog
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.google.android.gms.common.internal.Constants
|
||||||
|
import com.google.firebase.Firebase
|
||||||
|
import com.google.firebase.firestore.firestore
|
||||||
|
import `in`.sminnovations.smiadmin.data.Employee
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.tasks.await
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class ExportTask(private val fragment: Fragment) {
|
||||||
|
private val companyName = "ShanMukha Innovations"
|
||||||
|
private val db = Firebase.firestore
|
||||||
|
private var currentExcelTask: Job? = null
|
||||||
|
|
||||||
|
fun exportAttendance(year: Int, month: Int) {
|
||||||
|
// Cancel any existing task
|
||||||
|
currentExcelTask?.cancel()
|
||||||
|
|
||||||
|
// Show progress dialog
|
||||||
|
val progressDialog = ProgressDialog(fragment.requireContext()).apply {
|
||||||
|
setMessage("Generating attendance report...")
|
||||||
|
setCancelable(false)
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
|
currentExcelTask = fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
try {
|
||||||
|
// First get all employees
|
||||||
|
val employeesSnapshot = withContext(Dispatchers.IO) {
|
||||||
|
db.collection("employees")
|
||||||
|
.whereEqualTo("EmployeeStatus", true)
|
||||||
|
.get()
|
||||||
|
.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
val employees = employeesSnapshot.documents.map { doc ->
|
||||||
|
Employee(
|
||||||
|
Id = doc.getString("Id") ?: "Unknown",
|
||||||
|
Name = doc.getString("Name") ?: "Unknown"
|
||||||
|
)
|
||||||
|
}.sortedBy { it.Name }
|
||||||
|
|
||||||
|
// Switch to IO dispatcher for file operations
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val pdfGenerator = PDFGenerator(fragment.requireContext(), companyName)
|
||||||
|
pdfGenerator.generateAttendanceReport(employees, year, month)
|
||||||
|
}.also { pdfFile ->
|
||||||
|
// Switch back to Main dispatcher for UI updates
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
progressDialog.dismiss()
|
||||||
|
Toast.makeText(
|
||||||
|
fragment.context,
|
||||||
|
"Report saved in Downloads folder: ${pdfFile.name}",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
progressDialog.dismiss()
|
||||||
|
Toast.makeText(
|
||||||
|
fragment.context,
|
||||||
|
"Error: ${e.localizedMessage ?: "Unknown error occurred"}",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanup() {
|
||||||
|
currentExcelTask?.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ import kotlinx.coroutines.tasks.await
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
private val db = Firebase.firestore
|
private lateinit var exportTask: ExportTask
|
||||||
private val STORAGE_PERMISSION_CODE = 101
|
private val STORAGE_PERMISSION_CODE = 101
|
||||||
private var currentExcelTask: Job? = null
|
private var currentExcelTask: Job? = null
|
||||||
|
|
||||||
@@ -42,6 +42,7 @@ class HomeFragment : Fragment() {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
|
exportTask = ExportTask(this)
|
||||||
return inflater.inflate(R.layout.fragment_home, container, false)
|
return inflater.inflate(R.layout.fragment_home, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,39 +82,41 @@ class HomeFragment : Fragment() {
|
|||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
exportTask.exportAttendance(year, month)
|
||||||
currentExcelTask = lifecycleScope.launch {
|
|
||||||
try {
|
|
||||||
// First get all employees
|
|
||||||
val employeesSnapshot = withContext(Dispatchers.IO) {
|
|
||||||
db.collection("employees").get().await()
|
|
||||||
}
|
|
||||||
|
|
||||||
val employees = employeesSnapshot.documents.map { doc ->
|
|
||||||
Employee(
|
|
||||||
Id = doc.getString("Id") ?: "Unknown",
|
|
||||||
Name = doc.getString("Name") ?: "Unknown"
|
|
||||||
)
|
|
||||||
}.sortedBy { it.Name }
|
|
||||||
|
|
||||||
// Generate CSV file
|
|
||||||
val csvGenerator = CSVGenerator(requireContext())
|
|
||||||
val csvFile = csvGenerator.generateAttendanceReport(employees, year, month)
|
|
||||||
|
|
||||||
progressDialog.dismiss()
|
progressDialog.dismiss()
|
||||||
|
|
||||||
// Show success message with file location
|
// currentExcelTask = lifecycleScope.launch {
|
||||||
Toast.makeText(
|
// try {
|
||||||
context,
|
// // First get all employees
|
||||||
"Report saved in Downloads folder: ${csvFile.name}",
|
// val employeesSnapshot = withContext(Dispatchers.IO) {
|
||||||
Toast.LENGTH_LONG
|
// db.collection("employees").get().await()
|
||||||
).show()
|
// }
|
||||||
|
//
|
||||||
} catch (e: Exception) {
|
// val employees = employeesSnapshot.documents.map { doc ->
|
||||||
progressDialog.dismiss()
|
// Employee(
|
||||||
Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_LONG).show()
|
// Id = doc.getString("Id") ?: "Unknown",
|
||||||
}
|
// Name = doc.getString("Name") ?: "Unknown"
|
||||||
}
|
// )
|
||||||
|
// }.sortedBy { it.Name }
|
||||||
|
//
|
||||||
|
// // Generate CSV file
|
||||||
|
// val pdfGenerator = PDFGenerator(requireContext(), "ShanMukha Innovations")
|
||||||
|
// val pdfFile = pdfGenerator.generateAttendanceReport(employees, year, month)
|
||||||
|
//
|
||||||
|
// progressDialog.dismiss()
|
||||||
|
//
|
||||||
|
// // Show success message with file location
|
||||||
|
// Toast.makeText(
|
||||||
|
// context,
|
||||||
|
// "Report saved in Downloads folder: ${pdfFile.name}",
|
||||||
|
// Toast.LENGTH_LONG
|
||||||
|
// ).show()
|
||||||
|
//
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// progressDialog.dismiss()
|
||||||
|
// Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkStoragePermission(): Boolean {
|
private fun checkStoragePermission(): Boolean {
|
||||||
@@ -167,4 +170,8 @@ class HomeFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
exportTask.cleanup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
222
app/src/main/java/in/sminnovations/smiadmin/ui/PDFGenerator.kt
Normal file
222
app/src/main/java/in/sminnovations/smiadmin/ui/PDFGenerator.kt
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
package `in`.sminnovations.smiadmin.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.pdf.PdfDocument
|
||||||
|
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.firestore.firestore
|
||||||
|
import `in`.sminnovations.smiadmin.data.Employee
|
||||||
|
import kotlinx.coroutines.tasks.await
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class PDFGenerator(
|
||||||
|
private val context: Context,
|
||||||
|
private val companyName: String
|
||||||
|
) {
|
||||||
|
private lateinit var document: PdfDocument
|
||||||
|
private lateinit var currentPage: PdfDocument.Page
|
||||||
|
private lateinit var canvas: android.graphics.Canvas
|
||||||
|
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
|
||||||
|
private val primaryColor = Color.rgb(41, 128, 185) // Blue
|
||||||
|
private val secondaryColor = Color.rgb(44, 62, 80) // Dark Blue
|
||||||
|
private val borderColor = Color.rgb(189, 195, 199) // Light Gray
|
||||||
|
private val backgroundColor = Color.rgb(236, 240, 241) // Very Light Gray
|
||||||
|
|
||||||
|
suspend fun generateAttendanceReport(
|
||||||
|
employees: List<Employee>,
|
||||||
|
selectedYear: Int,
|
||||||
|
selectedMonth: Int
|
||||||
|
): File {
|
||||||
|
document = PdfDocument()
|
||||||
|
createNewPage()
|
||||||
|
|
||||||
|
// Setup paints
|
||||||
|
val headerPaint = Paint().apply {
|
||||||
|
textSize = 28f
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
|
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
||||||
|
color = primaryColor
|
||||||
|
}
|
||||||
|
|
||||||
|
val subHeaderPaint = Paint().apply {
|
||||||
|
textSize = 20f
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
|
color = secondaryColor
|
||||||
|
}
|
||||||
|
|
||||||
|
val tableHeaderPaint = Paint().apply {
|
||||||
|
textSize = 14f
|
||||||
|
textAlign = Paint.Align.LEFT
|
||||||
|
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
|
||||||
|
color = Color.WHITE
|
||||||
|
}
|
||||||
|
|
||||||
|
val tablePaint = Paint().apply {
|
||||||
|
textSize = 12f
|
||||||
|
textAlign = Paint.Align.LEFT
|
||||||
|
color = secondaryColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw headers and table
|
||||||
|
drawPageHeaders(headerPaint, subHeaderPaint, selectedYear, selectedMonth)
|
||||||
|
drawTableHeaders(tableHeaderPaint)
|
||||||
|
|
||||||
|
// Process each employee
|
||||||
|
employees.forEach { employee ->
|
||||||
|
if (currentY > pageHeight - 100) {
|
||||||
|
finishCurrentPage()
|
||||||
|
createNewPage()
|
||||||
|
drawPageHeaders(headerPaint, subHeaderPaint, selectedYear, selectedMonth)
|
||||||
|
drawTableHeaders(tableHeaderPaint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get attendance data
|
||||||
|
val snapshot = Firebase.firestore
|
||||||
|
.collection("attendance_${employee.Id}")
|
||||||
|
.get()
|
||||||
|
.await()
|
||||||
|
|
||||||
|
val monthStr = String.format("%02d", selectedMonth)
|
||||||
|
val presentDates = snapshot.documents
|
||||||
|
.filter { doc ->
|
||||||
|
doc.id.matches(Regex("\\d{2}-$monthStr-$selectedYear"))
|
||||||
|
}
|
||||||
|
.map { doc ->
|
||||||
|
doc.id.split("-")[0].toInt()
|
||||||
|
}
|
||||||
|
.sorted()
|
||||||
|
|
||||||
|
// Draw row background
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish and save document
|
||||||
|
finishCurrentPage()
|
||||||
|
|
||||||
|
val fileName = "Attendance_Summary_${selectedYear}_${selectedMonth}.pdf"
|
||||||
|
val file = File(
|
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
|
||||||
|
fileName
|
||||||
|
)
|
||||||
|
|
||||||
|
FileOutputStream(file).use { outputStream ->
|
||||||
|
document.writeTo(outputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.close()
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNewPage() {
|
||||||
|
val pageInfo = PdfDocument.PageInfo.Builder(pageWidth.toInt(), pageHeight.toInt(), pageNumber++).create()
|
||||||
|
currentPage = document.startPage(pageInfo)
|
||||||
|
canvas = currentPage.canvas
|
||||||
|
currentY = 120f
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finishCurrentPage() {
|
||||||
|
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