generate pdf code added

This commit is contained in:
chandrashekhar reddy
2025-01-07 15:14:13 +05:30
parent ba117159ea
commit eba4783a88
5 changed files with 346 additions and 212 deletions

View File

@@ -13,8 +13,8 @@ android {
applicationId "in.sminnovations.smiadmin"
minSdk 26
targetSdk 34
versionCode 1
versionName "1.0"
versionCode 2
versionName "1.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -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
}
}
}
}

View 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()
}
}

View File

@@ -33,7 +33,7 @@ import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
class HomeFragment : Fragment() {
private val db = Firebase.firestore
private lateinit var exportTask: ExportTask
private val STORAGE_PERMISSION_CODE = 101
private var currentExcelTask: Job? = null
@@ -42,6 +42,7 @@ class HomeFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
exportTask = ExportTask(this)
return inflater.inflate(R.layout.fragment_home, container, false)
}
@@ -81,39 +82,41 @@ class HomeFragment : Fragment() {
setCancelable(false)
show()
}
exportTask.exportAttendance(year, month)
progressDialog.dismiss()
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()
// Show success message with file location
Toast.makeText(
context,
"Report saved in Downloads folder: ${csvFile.name}",
Toast.LENGTH_LONG
).show()
} catch (e: Exception) {
progressDialog.dismiss()
Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_LONG).show()
}
}
// 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 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 {
@@ -167,4 +170,8 @@ class HomeFragment : Fragment() {
}
}
}
override fun onDestroy() {
super.onDestroy()
exportTask.cleanup()
}
}

View 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
}
}