diff --git a/.idea/other.xml b/.idea/other.xml
index 50032ab..720dd0e 100644
--- a/.idea/other.xml
+++ b/.idea/other.xml
@@ -157,6 +157,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -190,6 +201,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -245,6 +267,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 15ab599..282e5e2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -13,8 +13,8 @@ android {
applicationId "in.sminnovations.smiadmin"
minSdk 26
targetSdk 34
- versionCode 2
- versionName "1.1"
+ versionCode 3
+ versionName "1.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -55,5 +55,6 @@ dependencies {
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
+ implementation 'com.google.zxing:core:3.4.1'
}
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/smiadmin/data/Employee.kt b/app/src/main/java/in/sminnovations/smiadmin/data/Employee.kt
index 8bdaf7d..d8f7406 100644
--- a/app/src/main/java/in/sminnovations/smiadmin/data/Employee.kt
+++ b/app/src/main/java/in/sminnovations/smiadmin/data/Employee.kt
@@ -1,7 +1,13 @@
package `in`.sminnovations.smiadmin.data
data class Employee(
- val Id: String = "",
+ var Id: String = "",
val Name: String = "",
- var EmployeeStatus: Boolean = false
+ var EmployeeStatus: Boolean = false,
+ var Designation:String = "",
+ var DateOfJoining: String = "",
+ var dateOfBirth: String = "",
+ var Mobile: String = "",
+ var Email: String = "",
+ var CreatedAt: String = "",
)
diff --git a/app/src/main/java/in/sminnovations/smiadmin/ui/adapter/EmployeeQrAdapter.kt b/app/src/main/java/in/sminnovations/smiadmin/ui/adapter/EmployeeQrAdapter.kt
new file mode 100644
index 0000000..db625c2
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/smiadmin/ui/adapter/EmployeeQrAdapter.kt
@@ -0,0 +1,56 @@
+package `in`.sminnovations.smiadmin.ui.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+
+import androidx.recyclerview.widget.RecyclerView
+import `in`.sminnovations.smiadmin.data.Employee
+import `in`.sminnovations.smiadmin.databinding.ItemEmployeeQrBinding
+
+// EmployeeAdapter.kt (remains the same)
+class EmployeeQrAdapter(
+ private val onShareClick: (Employee) -> Unit,
+ private val onItemClick: (Employee) -> Unit
+) : ListAdapter(EmployeeDiffCallback()) {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmployeeViewHolder {
+ val binding = ItemEmployeeQrBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ )
+ return EmployeeViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: EmployeeViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ inner class EmployeeViewHolder(
+ private val binding: ItemEmployeeQrBinding
+ ) : RecyclerView.ViewHolder(binding.root) {
+
+ fun bind(employee: Employee) {
+ binding.apply {
+ textEmployeeName.text = employee.Name
+ textEmployeeId.text = employee.Id
+
+ buttonShare.setOnClickListener { onShareClick(employee) }
+ root.setOnClickListener { onItemClick(employee) }
+ }
+ }
+ }
+}
+
+// EmployeeDiffCallback.kt (remains the same)
+class EmployeeDiffCallback : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: Employee, newItem: Employee): Boolean {
+ return oldItem.Id == newItem.Id
+ }
+
+ override fun areContentsTheSame(oldItem: Employee, newItem: Employee): Boolean {
+ return oldItem == newItem
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/AddEmployeeFragment.kt b/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/AddEmployeeFragment.kt
new file mode 100644
index 0000000..d4cca60
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/AddEmployeeFragment.kt
@@ -0,0 +1,169 @@
+package `in`.sminnovations.smiadmin.ui.fragments
+
+import android.app.DatePickerDialog
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.navigation.fragment.findNavController
+import com.google.firebase.firestore.FirebaseFirestore
+import `in`.sminnovations.smiadmin.R
+import `in`.sminnovations.smiadmin.data.Employee
+import `in`.sminnovations.smiadmin.databinding.FragmentAddEmployeeBinding
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Date
+import java.util.Locale
+
+class AddEmployeeFragment : Fragment() {
+
+ private var _binding: FragmentAddEmployeeBinding? = null
+ private val binding get() = _binding!!
+
+ private val db = FirebaseFirestore.getInstance()
+ private var dateOfBirth: Calendar? = null
+ private var dateOfJoining: Calendar? = null
+ private val dateFormatter = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentAddEmployeeBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setupDatePickers()
+ setupSubmitButton()
+ }
+
+ private fun setupDatePickers() {
+ binding.etDateOfBirth.setOnClickListener {
+ showDatePicker(true)
+ }
+
+ binding.etDateOfJoining.setOnClickListener {
+ showDatePicker(false)
+ }
+ }
+
+ private fun showDatePicker(isDateOfBirth: Boolean) {
+ val calendar = Calendar.getInstance()
+
+ DatePickerDialog(
+ requireContext(),
+ { _, year, month, day ->
+ calendar.set(year, month, day)
+ val formattedDate = dateFormatter.format(calendar.time)
+
+ if (isDateOfBirth) {
+ dateOfBirth = calendar.clone() as Calendar
+ binding.etDateOfBirth.setText(formattedDate)
+ } else {
+ dateOfJoining = calendar.clone() as Calendar
+ binding.etDateOfJoining.setText(formattedDate)
+ }
+ },
+ calendar.get(Calendar.YEAR),
+ calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH)
+ ).show()
+ }
+
+ private fun generateEmployeeId(name: String, phone: String): String {
+ val namePrefix = name.take(4).uppercase()
+ val phoneSuffix = phone.takeLast(4)
+ return "$namePrefix$phoneSuffix"
+ }
+
+ private fun validateInputs(): Boolean {
+ var isValid = true
+
+ with(binding) {
+ if (etEmployeeName.text.isNullOrBlank()) {
+ etEmployeeName.error = "Name is required"
+ isValid = false
+ }
+
+ if (etEmployeeEmail.text.isNullOrBlank()) {
+ etEmployeeEmail.error = "Email is required"
+ isValid = false
+ } else if (!android.util.Patterns.EMAIL_ADDRESS.matcher(etEmployeeEmail.text.toString()).matches()) {
+ etEmployeeEmail.error = "Invalid email format"
+ isValid = false
+ }
+
+ if (etEmployeePhone.text.isNullOrBlank()) {
+ etEmployeePhone.error = "Phone number is required"
+ isValid = false
+ }
+
+ if (etEmployeePosition.text.isNullOrBlank()) {
+ etEmployeePosition.error = "Position is required"
+ isValid = false
+ }
+
+ if (dateOfBirth == null) {
+ etDateOfBirth.error = "Date of Birth is required"
+ isValid = false
+ }
+
+ if (dateOfJoining == null) {
+ etDateOfJoining.error = "Date of Joining is required"
+ isValid = false
+ }
+ }
+
+ return isValid
+ }
+
+ private fun saveEmployee() {
+ binding.btnSubmit.isEnabled = false // Prevent double submission
+
+ val name = binding.etEmployeeName.text.toString().trim()
+ val phone = binding.etEmployeePhone.text.toString().trim()
+ val employeeId = generateEmployeeId(name, phone)
+
+ val employee = Employee(
+ Id = employeeId,
+ Name = name,
+ Email = binding.etEmployeeEmail.text.toString().trim(),
+ Mobile = phone,
+ Designation = binding.etEmployeePosition.text.toString().trim(),
+ dateOfBirth = dateFormatter.format(dateOfBirth?.time ?: Date()),
+ DateOfJoining = dateFormatter.format(dateOfJoining?.time ?: Date()),
+ CreatedAt = dateFormatter.format(Date()) // Current date in dd/MM/yyyy format
+ )
+
+ db.collection("employees")
+ .document(employeeId)
+ .set(employee)
+ .addOnSuccessListener {
+ Toast.makeText(context, "Employee added successfully", Toast.LENGTH_SHORT).show()
+ findNavController().navigateUp()
+ }
+ .addOnFailureListener { e ->
+ binding.btnSubmit.isEnabled = true
+ Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ private fun setupSubmitButton() {
+ binding.btnSubmit.setOnClickListener {
+ if (validateInputs()) {
+ saveEmployee()
+ }
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/HomeFragment.kt b/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/HomeFragment.kt
index 535adb3..0119ef2 100644
--- a/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/HomeFragment.kt
+++ b/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/HomeFragment.kt
@@ -46,7 +46,12 @@ class HomeFragment : Fragment() {
view.findViewById(R.id.cardAttendanceReport).setOnClickListener {
findNavController().navigate(R.id.action_home_to_employee_attendance_list)
}
-
+ view.findViewById(R.id.cardAddEmployee).setOnClickListener {
+ findNavController().navigate(R.id.action_home_to_add_employee)
+ }
+ view.findViewById(R.id.cardEmployeeQr).setOnClickListener {
+ findNavController().navigate(R.id.action_to_employeeQrFragment)
+ }
view.findViewById(R.id.cardDownloadReport).setOnClickListener {
if (checkStoragePermission()) {
showMonthYearPicker()
diff --git a/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/ShareQrFragment.kt b/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/ShareQrFragment.kt
new file mode 100644
index 0000000..4df21cd
--- /dev/null
+++ b/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/ShareQrFragment.kt
@@ -0,0 +1,126 @@
+package `in`.sminnovations.smiadmin.ui.fragments
+
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.net.Uri
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.Toast
+import androidx.core.content.FileProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.firebase.firestore.FirebaseFirestore
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.qrcode.QRCodeWriter
+import `in`.sminnovations.smiadmin.data.Employee
+import `in`.sminnovations.smiadmin.databinding.FragmentShareQrBinding
+import `in`.sminnovations.smiadmin.ui.adapter.EmployeeQrAdapter
+import java.io.File
+import java.io.FileOutputStream
+
+class ShareQrFragment : Fragment() {
+
+ private lateinit var binding: FragmentShareQrBinding
+ private lateinit var employeeAdapter: EmployeeQrAdapter
+ private val db = FirebaseFirestore.getInstance()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentShareQrBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupRecyclerView()
+ fetchEmployees()
+ }
+
+ private fun setupRecyclerView() {
+ employeeAdapter = EmployeeQrAdapter(
+ onShareClick = { employee -> shareQRCode(employee) },
+ onItemClick = { employee -> showQRCode(employee) }
+ )
+ binding.recyclerViewEmployees.apply {
+ adapter = employeeAdapter
+ layoutManager = LinearLayoutManager(context)
+ }
+ }
+
+ private fun fetchEmployees() {
+ db.collection("employees")
+ .addSnapshotListener { snapshot, e ->
+ if (e != null) {
+ // Handle error
+ Toast.makeText(context, "Error fetching employees: ${e.message}", Toast.LENGTH_SHORT).show()
+ return@addSnapshotListener
+ }
+
+ val employeeList = snapshot?.documents?.mapNotNull { doc ->
+ doc.toObject(Employee::class.java)?.takeIf { it.EmployeeStatus }?.apply {
+ Id = doc.id
+ }
+ } ?: emptyList()
+
+ employeeAdapter.submitList(employeeList)
+ }
+ }
+
+ private fun generateQRCode(employeeId: String): Bitmap {
+ val qrCodeWriter = QRCodeWriter()
+ val bitMatrix = qrCodeWriter.encode(employeeId, BarcodeFormat.QR_CODE, 512, 512)
+ val width = bitMatrix.width
+ val height = bitMatrix.height
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
+
+ for (x in 0 until width) {
+ for (y in 0 until height) {
+ bitmap.setPixel(x, y, if (bitMatrix[x, y]) Color.BLACK else Color.WHITE)
+ }
+ }
+ return bitmap
+ }
+
+ private fun showQRCode(employee: Employee) {
+ val qrBitmap = generateQRCode(employee.Id)
+ val dialog = MaterialAlertDialogBuilder(requireContext())
+ .setTitle("${employee.Name}'s QR Code")
+ .setView(ImageView(context).apply {
+ setImageBitmap(qrBitmap)
+ layoutParams = ViewGroup.LayoutParams(512, 512)
+ })
+ .setPositiveButton("Close") { dialog, _ -> dialog.dismiss() }
+ .create()
+ dialog.show()
+ }
+
+ private fun shareQRCode(employee: Employee) {
+ val qrBitmap = generateQRCode(employee.Id)
+ val shareIntent = Intent(Intent.ACTION_SEND).apply {
+ type = "image/*"
+ putExtra(Intent.EXTRA_STREAM, saveQRCodeToCache(qrBitmap, employee.Id))
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+ startActivity(Intent.createChooser(shareIntent, "Share QR Code"))
+ }
+
+ private fun saveQRCodeToCache(bitmap: Bitmap, employeeId: String): Uri {
+ val file = File(requireContext().cacheDir, "qr_${employeeId}.png")
+ FileOutputStream(file).use { stream ->
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
+ }
+ return FileProvider.getUriForFile(
+ requireContext(),
+ "${requireContext().packageName}.provider",
+ file
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/attendance/EmployeeAttendanceListFragment.kt b/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/attendance/EmployeeAttendanceListFragment.kt
index 1ad40e6..3f6994c 100644
--- a/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/attendance/EmployeeAttendanceListFragment.kt
+++ b/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/attendance/EmployeeAttendanceListFragment.kt
@@ -57,11 +57,16 @@ class EmployeeAttendanceListFragment : Fragment() {
.get()
.addOnSuccessListener { snapshot ->
binding.progressBar.visibility = View.GONE
- val employees = snapshot.documents.map { doc ->
- Employee(
- Id = doc.getString("Id") ?: "Unknown",
- Name = doc.getString("Name") ?: "Unknown"
- )
+ val employees = snapshot.documents.mapNotNull { doc ->
+ val status = doc.getBoolean("EmployeeStatus") ?: true // Default to true if field is missing
+ if (status) {
+ Employee(
+ Id = doc.getString("Id") ?: doc.getString("id") ?: "",
+ Name = doc.getString("Name") ?: doc.getString("name") ?: ""
+ )
+ } else {
+ null // Exclude employees with EmployeeStatus == false
+ }
}.sortedBy { it.Name }
adapter.updateEmployees(employees)
}
diff --git a/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/attendance/EmployeeListFragment.kt b/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/attendance/EmployeeListFragment.kt
index 1872e7b..486d0fb 100644
--- a/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/attendance/EmployeeListFragment.kt
+++ b/app/src/main/java/in/sminnovations/smiadmin/ui/fragments/attendance/EmployeeListFragment.kt
@@ -58,9 +58,9 @@ class EmployeeListFragment : Fragment() {
snapshot?.let {
val employeeList = snapshot.documents.map { doc ->
Employee(
- Id = doc.getString("Id") ?: "",
- Name = doc.getString("Name") ?: "",
- EmployeeStatus = doc.getBoolean("EmployeeStatus") ?: false
+ Id = doc.getString("Id") ?: doc.getString("id") ?: "",
+ Name = doc.getString("Name") ?: doc.getString("name") ?: "",
+ EmployeeStatus = doc.getBoolean("EmployeeStatus") ?: doc.getBoolean("employeestatus") ?: false
)
}
adapter.updateEmployees(employeeList)
diff --git a/app/src/main/java/in/sminnovations/smiadmin/ui/utils/ExportTask.kt b/app/src/main/java/in/sminnovations/smiadmin/ui/utils/ExportTask.kt
index 78ae5df..855edfb 100644
--- a/app/src/main/java/in/sminnovations/smiadmin/ui/utils/ExportTask.kt
+++ b/app/src/main/java/in/sminnovations/smiadmin/ui/utils/ExportTask.kt
@@ -1,7 +1,16 @@
package `in`.sminnovations.smiadmin.ui.utils
+import android.Manifest
import android.app.ProgressDialog
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Environment
import android.widget.Toast
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.google.firebase.Firebase
@@ -13,16 +22,38 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext
+import java.io.File
+import java.io.IOException
class ExportTask(private val fragment: Fragment) {
private val db = Firebase.firestore
private var currentExcelTask: Job? = null
+ // Add permission check
+ private fun checkStoragePermission(): Boolean {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ true // Android 10 and above don't need explicit storage permission for Downloads
+ } else {
+ ContextCompat.checkSelfPermission(
+ fragment.requireContext(),
+ Manifest.permission.WRITE_EXTERNAL_STORAGE
+ ) == PackageManager.PERMISSION_GRANTED
+ }
+ }
+
fun exportAttendance(year: Int, month: Int) {
+ if (!checkStoragePermission()) {
+ ActivityCompat.requestPermissions(
+ fragment.requireActivity(),
+ arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
+ STORAGE_PERMISSION_CODE
+ )
+ return
+ }
+
// Cancel any existing task
currentExcelTask?.cancel()
- // Show progress dialog
val progressDialog = ProgressDialog(fragment.requireContext()).apply {
setMessage("Generating attendance report...")
setCancelable(false)
@@ -46,19 +77,78 @@ class ExportTask(private val fragment: Fragment) {
)
}.sortedBy { it.Name }
- // Switch to IO dispatcher for file operations
withContext(Dispatchers.IO) {
val pdfGenerator = PDFGenerator(fragment.requireContext(), Constant.COMPANY_NAME)
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()
+ withContext(Dispatchers.IO) {
+ try {
+ // Verify file exists and is readable
+ if (!pdfFile.exists() || !pdfFile.canRead()) {
+ throw IOException("Generated PDF file is not accessible")
+ }
+
+ val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ val pdfFileInDownloads = File(downloadsDir, pdfFile.name)
+
+ // Copy file to Downloads if needed
+ if (!pdfFileInDownloads.exists()) {
+ pdfFile.copyTo(pdfFileInDownloads, overwrite = true)
+ }
+
+ // Verify the file was copied successfully
+ if (!pdfFileInDownloads.exists() || !pdfFileInDownloads.canRead()) {
+ throw IOException("Failed to copy PDF to Downloads folder")
+ }
+
+ val emailIntent = Intent(Intent.ACTION_SEND).apply {
+ type = "application/pdf"
+ putExtra(Intent.EXTRA_EMAIL, arrayOf("admin@sminnovations.in"))
+ putExtra(Intent.EXTRA_SUBJECT, "Attendance Report - $month $year")
+ putExtra(Intent.EXTRA_TEXT, "Please find attached the attendance report.")
+
+ try {
+ // Convert file URI to content URI for sharing
+ val contentUri = FileProvider.getUriForFile(
+ fragment.requireContext(),
+ "${fragment.requireContext().packageName}.provider",
+ pdfFileInDownloads
+ )
+ putExtra(Intent.EXTRA_STREAM, contentUri)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ } catch (e: IllegalArgumentException) {
+ throw IOException("FileProvider not properly configured: ${e.message}")
+ }
+ }
+
+ // Switch to Main dispatcher for starting the email activity
+ withContext(Dispatchers.Main) {
+ progressDialog.dismiss()
+ try {
+ fragment.startActivity(Intent.createChooser(emailIntent, "Send Email"))
+ Toast.makeText(
+ fragment.context,
+ "Report saved in Downloads folder: ${pdfFile.name}",
+ Toast.LENGTH_LONG
+ ).show()
+ } catch (e: ActivityNotFoundException) {
+ Toast.makeText(
+ fragment.context,
+ "No email app found",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ progressDialog.dismiss()
+ Toast.makeText(
+ fragment.context,
+ "Error sending email: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
}
}
} catch (e: Exception) {
@@ -78,4 +168,8 @@ class ExportTask(private val fragment: Fragment) {
fun cleanup() {
currentExcelTask?.cancel()
}
+
+ companion object {
+ private const val STORAGE_PERMISSION_CODE = 101
+ }
}
diff --git a/app/src/main/java/in/sminnovations/smiadmin/ui/utils/PDFGenerator.kt b/app/src/main/java/in/sminnovations/smiadmin/ui/utils/PDFGenerator.kt
index 18e49fa..afee9b0 100644
--- a/app/src/main/java/in/sminnovations/smiadmin/ui/utils/PDFGenerator.kt
+++ b/app/src/main/java/in/sminnovations/smiadmin/ui/utils/PDFGenerator.kt
@@ -229,7 +229,7 @@ class PDFGenerator(
canvas.drawText(
employee.Name,
margin + 15f,
- currentY + 35f,
+ currentY + 10f,
regularPaint
)
@@ -237,7 +237,7 @@ class PDFGenerator(
canvas.drawText(
"${presentDates.size} days",
margin + 195f,
- currentY + 35f,
+ currentY + 10f,
highlightPaint
)
@@ -249,7 +249,7 @@ class PDFGenerator(
}
}
- var dateY = currentY + 25f
+ var dateY = currentY + 10f
datesText.split("\n").forEach { line ->
canvas.drawText(
line,
@@ -257,10 +257,10 @@ class PDFGenerator(
dateY,
regularPaint
)
- dateY += 15f
+ dateY += 10f
}
- currentY += rowHeight + 5f
+ currentY += rowHeight + 10f
}
private fun drawFooter(paint: Paint) {
diff --git a/app/src/main/res/drawable/baseline_share_24.xml b/app/src/main/res/drawable/baseline_share_24.xml
new file mode 100644
index 0000000..74753b7
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_share_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_add_employee.xml b/app/src/main/res/layout/fragment_add_employee.xml
new file mode 100644
index 0000000..e239651
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_employee.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_employee_attendance_list.xml b/app/src/main/res/layout/fragment_employee_attendance_list.xml
index 7c11275..2cd6c0e 100644
--- a/app/src/main/res/layout/fragment_employee_attendance_list.xml
+++ b/app/src/main/res/layout/fragment_employee_attendance_list.xml
@@ -6,6 +6,7 @@
diff --git a/app/src/main/res/layout/fragment_employee_list.xml b/app/src/main/res/layout/fragment_employee_list.xml
index 7c11275..fe6ebe1 100644
--- a/app/src/main/res/layout/fragment_employee_list.xml
+++ b/app/src/main/res/layout/fragment_employee_list.xml
@@ -5,6 +5,7 @@
android:layout_height="match_parent">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_share_qr.xml b/app/src/main/res/layout/fragment_share_qr.xml
new file mode 100644
index 0000000..5661bb9
--- /dev/null
+++ b/app/src/main/res/layout/fragment_share_qr.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_employee_qr.xml b/app/src/main/res/layout/item_employee_qr.xml
new file mode 100644
index 0000000..eda5d33
--- /dev/null
+++ b/app/src/main/res/layout/item_employee_qr.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml
index 53b29bd..03b848a 100644
--- a/app/src/main/res/navigation/mobile_navigation.xml
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -14,6 +14,12 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file