new version 3.1.1
This commit is contained in:
77
.idea/other.xml
generated
77
.idea/other.xml
generated
@@ -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" />
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user