new version 3.1.1

This commit is contained in:
sathwikcs
2025-03-03 14:37:41 +05:30
parent 27058baf99
commit b395df6268
4 changed files with 177 additions and 37 deletions

77
.idea/other.xml generated
View File

@@ -47,6 +47,28 @@
<option name="screenX" value="1840" /> <option name="screenX" value="1840" />
<option name="screenY" value="2944" /> <option name="screenY" value="2944" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="a15" />
<option name="id" value="a15" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A15" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="a35x" />
<option name="id" value="a35x" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="A35" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="31" /> <option name="api" value="31" />
<option name="brand" value="samsung" /> <option name="brand" value="samsung" />
@@ -245,6 +267,17 @@
<option name="screenX" value="1080" /> <option name="screenX" value="1080" />
<option name="screenY" value="2340" /> <option name="screenY" value="2340" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="gts7xllite" />
<option name="id" value="gts7xllite" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-T738U" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="33" /> <option name="api" value="33" />
<option name="brand" value="samsung" /> <option name="brand" value="samsung" />
@@ -256,6 +289,17 @@
<option name="screenX" value="1848" /> <option name="screenX" value="1848" />
<option name="screenY" value="2960" /> <option name="screenY" value="2960" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="gts8wifi" />
<option name="id" value="gts8wifi" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S8" />
<option name="screenDensity" value="274" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="34" /> <option name="api" value="34" />
<option name="brand" value="google" /> <option name="brand" value="google" />
@@ -311,6 +355,17 @@
<option name="screenX" value="720" /> <option name="screenX" value="720" />
<option name="screenY" value="1600" /> <option name="screenY" value="1600" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="o1q" />
<option name="id" value="o1q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S21" />
<option name="screenDensity" value="421" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="31" /> <option name="api" value="31" />
<option name="brand" value="google" /> <option name="brand" value="google" />
@@ -367,6 +422,17 @@
<option name="screenY" value="384" /> <option name="screenY" value="384" />
<option name="type" value="WEAR_OS" /> <option name="type" value="WEAR_OS" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="r11q" />
<option name="id" value="r11q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-S711U" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="30" /> <option name="api" value="30" />
<option name="brand" value="google" /> <option name="brand" value="google" />
@@ -389,6 +455,17 @@
<option name="screenX" value="1080" /> <option name="screenX" value="1080" />
<option name="screenY" value="2400" /> <option name="screenY" value="2400" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="t2q" />
<option name="id" value="t2q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S21 Plus" />
<option name="screenDensity" value="394" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="33" /> <option name="api" value="33" />
<option name="brand" value="google" /> <option name="brand" value="google" />

View File

@@ -14,9 +14,9 @@ android {
applicationId = "in.sminnovations.attendanceapp.user" applicationId = "in.sminnovations.attendanceapp.user"
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 4 versionCode = 11
versionName = "3.1.1" versionName = "3.1.4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {

View File

@@ -9,11 +9,11 @@ import com.google.firebase.Timestamp
import com.google.firebase.firestore.DocumentSnapshot import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.FirebaseFirestore
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import `in`.sminnovations.attendanceapp.screens.home.data.AttendanceUiState
import `in`.sminnovations.attendanceapp.utils.AppUpdater import `in`.sminnovations.attendanceapp.utils.AppUpdater
import `in`.sminnovations.attendanceapp.utils.NetworkMonitor import `in`.sminnovations.attendanceapp.utils.NetworkMonitor
import `in`.sminnovations.attendanceapp.utils.UpdateInfo
import `in`.sminnovations.attendanceapp.screens.home.data.AttendanceUiState
import `in`.sminnovations.attendanceapp.utils.UpdateChecker import `in`.sminnovations.attendanceapp.utils.UpdateChecker
import `in`.sminnovations.attendanceapp.utils.UpdateInfo
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@@ -31,6 +31,8 @@ import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
const val timeFormat = "dd MMMM yyyy hh:mm:ss a"
@HiltViewModel @HiltViewModel
class HomeViewModel @Inject constructor( class HomeViewModel @Inject constructor(
applicationContext: Context, applicationContext: Context,
@@ -78,12 +80,15 @@ class HomeViewModel @Inject constructor(
is AppUpdater.UpdaterState.Downloading -> { is AppUpdater.UpdaterState.Downloading -> {
_downloadProgress.value = state.progress _downloadProgress.value = state.progress
} }
is AppUpdater.UpdaterState.Success -> { is AppUpdater.UpdaterState.Success -> {
_downloadProgress.value = 1f _downloadProgress.value = 1f
} }
is AppUpdater.UpdaterState.Error -> { is AppUpdater.UpdaterState.Error -> {
_downloadProgress.value = 0f _downloadProgress.value = 0f
} }
AppUpdater.UpdaterState.Idle -> { AppUpdater.UpdaterState.Idle -> {
// No op // No op
} }
@@ -154,7 +159,8 @@ class HomeViewModel @Inject constructor(
processQRCode(value) processQRCode(value)
} }
} }
private fun processQRCode(userId: String) {
private fun processQRCode(userId: String) {
if (userId.isBlank()) return if (userId.isBlank()) return
viewModelScope.launch { viewModelScope.launch {
@@ -247,7 +253,7 @@ class HomeViewModel @Inject constructor(
private fun formatTimestamp(timestamp: Timestamp): String { private fun formatTimestamp(timestamp: Timestamp): String {
return SimpleDateFormat( return SimpleDateFormat(
"dd MMMM yyyy hh:mm:ss a", Locale.getDefault() timeFormat, Locale.getDefault()
).format(timestamp.toDate()) ).format(timestamp.toDate())
} }
@@ -259,30 +265,59 @@ class HomeViewModel @Inject constructor(
// } // }
private suspend fun handleExistingAttendance(document: DocumentSnapshot) { private suspend fun handleExistingAttendance(document: DocumentSnapshot) {
val data = document.data ?: return val data = document.data ?: return
val checkIns = data.filterKeys { it.startsWith("checkIn") }.size val checkIns = data.filterKeys { it.startsWith("checkIn") }.size
val checkOuts = data.filterKeys { it.startsWith("checkOut") }.size val checkOuts = data.filterKeys { it.startsWith("checkOut") }.size
val checkIn = document.getString("checkIn$checkIns")!!
if (checkIns > checkOuts) { if (isTimeDifferenceGreaterThanSec(givenTimeStr = checkIn, currentTimeStr = formatTimestamp(Timestamp.now()), secVal = 5L)) {
processCheckOut(document, checkIns) if (!isTimeDifferenceGreaterThanMinutes(
} else { givenTimeStr = checkIn,
processCheckIn(document, checkIns + 1) currentTimeStr = formatTimestamp(Timestamp.now()),
} minutesVal = 5L
)
) {
handleError("Wait 5 minutes before logging off")
} else {
if (checkIns > checkOuts) {
processCheckOut(document, checkIns)
} else {
processCheckIn(document, checkIns + 1)
}
}
}
} }
// private fun isTimeDifferenceGreaterThanMinutes(givenTimeStr: String, currentTimeStr: String = formatTimestamp(Timestamp.now()), minutes: Long): Boolean {
// val formatter = DateTimeFormatter.ofPattern("dd MMMM yyyy hh:mm:ss a") private fun isTimeDifferenceGreaterThanMinutes(
// givenTimeStr: String,
// val givenTime = LocalDateTime.parse(givenTimeStr, formatter) currentTimeStr: String = formatTimestamp(Timestamp.now()),
// val currentTime = LocalDateTime.parse(currentTimeStr, formatter) minutesVal: Long
// ): Boolean {
// val timeDifference = abs(ChronoUnit.MINUTES.between(givenTime, currentTime)) val formatter = DateTimeFormatter.ofPattern(timeFormat)
//
// return timeDifference > minutes val givenTime = LocalDateTime.parse(givenTimeStr, formatter)
// } val currentTime = LocalDateTime.parse(currentTimeStr, formatter)
val timeDifference = abs(ChronoUnit.MINUTES.between(givenTime, currentTime))
return timeDifference > minutesVal
}
private fun isTimeDifferenceGreaterThanSec(
givenTimeStr: String,
currentTimeStr: String = formatTimestamp(Timestamp.now()),
secVal: Long
): Boolean {
val formatter = DateTimeFormatter.ofPattern(timeFormat)
val givenTime = LocalDateTime.parse(givenTimeStr, formatter)
val currentTime = LocalDateTime.parse(currentTimeStr, formatter)
val timeDifference = abs(ChronoUnit.SECONDS.between(givenTime, currentTime))
return timeDifference > secVal
}
private suspend fun handleNewAttendance(userId: String, currentDate: String) { private suspend fun handleNewAttendance(userId: String, currentDate: String) {
try { try {
@@ -307,7 +342,8 @@ class HomeViewModel @Inject constructor(
private suspend fun processCheckIn(document: DocumentSnapshot, checkInCount: Int) { private suspend fun processCheckIn(document: DocumentSnapshot, checkInCount: Int) {
try { try {
document.reference.update("checkIn$checkInCount", formatTimestamp(Timestamp.now())).await() document.reference.update("checkIn$checkInCount", formatTimestamp(Timestamp.now()))
.await()
_uiState.update { _uiState.update {
it.copy( it.copy(
isCheckedIn = true, qrCodeValue = null isCheckedIn = true, qrCodeValue = null
@@ -338,8 +374,8 @@ class HomeViewModel @Inject constructor(
} }
} }
private fun calculateTotalWork(document: DocumentSnapshot): Pair<String,Boolean> { private fun calculateTotalWork(document: DocumentSnapshot): Pair<String, Boolean> {
val formatter = DateTimeFormatter.ofPattern("dd MMMM yyyy hh:mm:ss a", Locale.getDefault()) val formatter = DateTimeFormatter.ofPattern(timeFormat, Locale.getDefault())
var totalMinutes = 0L var totalMinutes = 0L
for (i in 1..20) { for (i in 1..20) {
val checkIn = document.getString("checkIn$i") ?: break val checkIn = document.getString("checkIn$i") ?: break
@@ -351,8 +387,8 @@ class HomeViewModel @Inject constructor(
val hours = totalMinutes / 60 val hours = totalMinutes / 60
val minutes = totalMinutes % 60 val minutes = totalMinutes % 60
val isDayCompleted = totalMinutes >=420L val isDayCompleted = totalMinutes >= 420L
return Pair(String.format("%02d:%02d:00", hours, minutes),isDayCompleted) return Pair(String.format("%02d:%02d:00", hours, minutes), isDayCompleted)
} }

View File

@@ -1,10 +1,13 @@
package `in`.sminnovations.attendanceapp package `in`.sminnovations.attendanceapp
import com.google.firebase.Timestamp
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import java.text.SimpleDateFormat
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.Locale
import kotlin.math.abs import kotlin.math.abs
/** /**
@@ -19,14 +22,15 @@ class ExampleUnitTest {
assertEquals(4, 2 + 2) assertEquals(4, 2 + 2)
} }
// @Test @Test
// fun timeTest(){ fun timeTest(){
// val currentTime = "30 January 2025 11:01:55 am" val currentTime = "30 January 2025 10:30:55 am"
// val givenTime = "30 January 2025 10:30:55 am" val givenTime = "30 January 2025 10:31:01 am"
// val minutes = 20L val minutes = 7L
// val result = isTimeDifferenceGreaterThanMinutes(givenTime, currentTime, minutes) val result = isTimeDifferenceGreaterThanSec(givenTime, currentTime, minutes)
// assertEquals(true,result) println(result)
// } assertEquals(false,result)
}
// //
// private fun isTimeDifferenceGreaterThanMinutes(givenTimeStr: String, currentTimeStr: String, minutes: Long): Boolean { // private fun isTimeDifferenceGreaterThanMinutes(givenTimeStr: String, currentTimeStr: String, minutes: Long): Boolean {
// val formatter = DateTimeFormatter.ofPattern("dd MMMM yyyy hh:mm:ss a") // val formatter = DateTimeFormatter.ofPattern("dd MMMM yyyy hh:mm:ss a")
@@ -35,4 +39,27 @@ class ExampleUnitTest {
// val timeDifference = abs(ChronoUnit.MINUTES.between(givenTime, currentTime)) // val timeDifference = abs(ChronoUnit.MINUTES.between(givenTime, currentTime))
// return timeDifference > minutes // return timeDifference > minutes
// } // }
private fun isTimeDifferenceGreaterThanSec(
givenTimeStr: String,
currentTimeStr: String = formatTimestamp(Timestamp.now()),
sec: Long = 5
): Boolean {
val formatter = DateTimeFormatter.ofPattern("dd MMMM yyyy hh:mm:ss a")
val givenTime = LocalDateTime.parse(givenTimeStr, formatter)
val currentTime = LocalDateTime.parse(currentTimeStr, formatter)
val timeDifference = abs(ChronoUnit.SECONDS.between(givenTime, currentTime))
return timeDifference > sec
}
private fun formatTimestamp(timestamp: Timestamp): String {
return SimpleDateFormat(
"dd MMMM yyyy hh:mm:ss a", Locale.getDefault()
).format(timestamp.toDate())
}
} }