3 Commits

Author SHA1 Message Date
Sathwik C S
01a77a7bdb Update MainActivity.kt 2024-10-16 12:46:30 +00:00
chandrashekhar reddy
866db292ab init commit 2024-10-07 11:37:37 +05:30
chandrashekhar reddy
64de08275a init commit 2024-10-07 11:28:04 +05:30
71 changed files with 71 additions and 2680 deletions

View File

@@ -1,101 +0,0 @@
# This file is a template, and might need editing before it works on your project.
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Android.gitlab-ci.yml
# Read more about this script on this blog post https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/, by Jason Lenny
# If you are interested in using Android with FastLane for publishing take a look at the Android-Fastlane template.
image: eclipse-temurin:17-jdk-jammy
variables:
# ANDROID_COMPILE_SDK is the version of Android you're compiling with.
# It should match compileSdkVersion.
ANDROID_COMPILE_SDK: "34"
# ANDROID_BUILD_TOOLS is the version of the Android build tools you are using.
# It should match buildToolsVersion.
ANDROID_BUILD_TOOLS: "33.0.2"
# It's what version of the command line tools we're going to download from the official site.
# Official Site-> https://developer.android.com/studio/index.html
# There, look down below at the cli tools only, sdk tools package is of format:
# commandlinetools-os_type-ANDROID_SDK_TOOLS_latest.zip
# when the script was last modified for latest compileSdkVersion, it was which is written down below
ANDROID_SDK_TOOLS: "9477386"
# Packages installation before running script
before_script:
- apt-get --quiet update --yes
- apt-get --quiet install --yes wget unzip
# Setup path as android_home for moving/exporting the downloaded sdk into it
- export ANDROID_HOME="${PWD}/android-sdk-root"
# Create a new directory at specified location
- install -d $ANDROID_HOME
# Here we are installing androidSDK tools from official source,
# (the key thing here is the url from where you are downloading these sdk tool for command line, so please do note this url pattern there and here as well)
# after that unzipping those tools and
# then running a series of SDK manager commands to install necessary android SDK packages that'll allow the app to build
- wget --no-verbose --output-document=$ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}_latest.zip
- unzip -q -d "$ANDROID_HOME/cmdline-tools" "$ANDROID_HOME/cmdline-tools.zip"
- mv -T "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/tools"
- export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/cmdline-tools/tools/bin
# Nothing fancy here, just checking sdkManager version
- sdkmanager --version
# use yes to accept all licenses
- yes | sdkmanager --licenses > /dev/null || true
- sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}"
- sdkmanager "platform-tools"
- sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}"
# Not necessary, but just for surity
- chmod +x ./gradlew
# Basic android and gradle stuff
# Check linting
lintDebug:
interruptible: true
stage: build
script:
- ./gradlew -Pci --console=plain :app:lintDebug -PbuildDir=lint
artifacts:
paths:
- app/lint/reports/lint-results-debug.html
expose_as: "lint-report"
when: always
# Make Project
assembleDebug:
interruptible: true
stage: build
script:
- ./gradlew assembleDebug
artifacts:
paths:
- app/build/outputs/
# Run all tests, if any fails, interrupt the pipeline(fail it)
debugTests:
needs: [lintDebug, assembleDebug]
interruptible: true
stage: test
script:
- ./gradlew -Pci --console=plain :app:testDebug
artifacts:
paths:
- app/build/test-results/
publishTestResults:
stage: test
script:
- echo "Publishing JUnit test results"
needs: [debugTests]
artifacts:
when: always
reports:
junit: app/build/test-results/testDebugUnitTest/*.xml

View File

@@ -4,18 +4,15 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-02-06T09:50:04.018268400Z"> <DropdownSelection timestamp="2024-10-04T09:54:24.929499Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=R9ZW605ZHAY" /> <DeviceId pluginId="LocalEmulator" identifier="path=/Users/chandrashekharreddy/.android/avd/Pixel_4_API_30.avd" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>
<DialogSelection /> <DialogSelection />
</SelectionState> </SelectionState>
<SelectionState runConfigName="AnimatedPreloader">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates> </selectionStates>
</component> </component>
</project> </project>

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">

238
.idea/other.xml generated
View File

@@ -14,28 +14,6 @@
<option name="screenX" value="720" /> <option name="screenX" value="720" />
<option name="screenY" value="1280" /> <option name="screenY" value="1280" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="OnePlus" />
<option name="codename" value="OP5552L1" />
<option name="id" value="OP5552L1" />
<option name="manufacturer" value="OnePlus" />
<option name="name" value="CPH2415" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2412" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="OPPO" />
<option name="codename" value="OP573DL1" />
<option name="id" value="OP573DL1" />
<option name="manufacturer" value="OPPO" />
<option name="name" value="CPH2557" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="28" /> <option name="api" value="28" />
<option name="brand" value="DOCOMO" /> <option name="brand" value="DOCOMO" />
@@ -58,28 +36,6 @@
<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" />
@@ -102,28 +58,6 @@
<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="motorola" />
<option name="codename" value="arcfox" />
<option name="id" value="arcfox" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="razr plus 2024" />
<option name="screenDensity" value="360" />
<option name="screenX" value="1080" />
<option name="screenY" value="1272" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="motorola" />
<option name="codename" value="austin" />
<option name="id" value="austin" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g 5G (2022)" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="33" /> <option name="api" value="33" />
<option name="brand" value="samsung" /> <option name="brand" value="samsung" />
@@ -179,17 +113,6 @@
<option name="screenX" value="2220" /> <option name="screenX" value="2220" />
<option name="screenY" value="1080" /> <option name="screenY" value="1080" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm2q" />
<option name="id" value="dm2q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="S23 Plus" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="34" /> <option name="api" value="34" />
<option name="brand" value="samsung" /> <option name="brand" value="samsung" />
@@ -212,28 +135,6 @@
<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="e3q" />
<option name="id" value="e3q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S24 Ultra" />
<option name="screenDensity" value="450" />
<option name="screenX" value="1440" />
<option name="screenY" value="3120" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="eos" />
<option name="id" value="eos" />
<option name="manufacturer" value="Google" />
<option name="name" value="Eos" />
<option name="screenDensity" value="320" />
<option name="screenX" value="384" />
<option name="screenY" value="384" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="33" /> <option name="api" value="33" />
<option name="brand" value="google" /> <option name="brand" value="google" />
@@ -267,50 +168,6 @@
<option name="screenX" value="2208" /> <option name="screenX" value="2208" />
<option name="screenY" value="1840" /> <option name="screenY" value="1840" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="motorola" />
<option name="codename" value="fogona" />
<option name="id" value="fogona" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g play - 2024" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="g0q" />
<option name="id" value="g0q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-S906U1" />
<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="gta9pwifi" />
<option name="id" value="gta9pwifi" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="SM-X210" />
<option name="screenDensity" value="240" />
<option name="screenX" value="1200" />
<option name="screenY" value="1920" />
</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" />
@@ -322,28 +179,6 @@
<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>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="gts9fe" />
<option name="id" value="gts9fe" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S9 FE 5G" />
<option name="screenDensity" value="280" />
<option name="screenX" value="1440" />
<option name="screenY" value="2304" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="34" /> <option name="api" value="34" />
<option name="brand" value="google" /> <option name="brand" value="google" />
@@ -388,28 +223,6 @@
<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="33" />
<option name="brand" value="motorola" />
<option name="codename" value="maui" />
<option name="id" value="maui" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="moto g play - 2023" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</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" />
@@ -443,17 +256,6 @@
<option name="screenX" value="1812" /> <option name="screenX" value="1812" />
<option name="screenY" value="2176" /> <option name="screenY" value="2176" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q6q" />
<option name="id" value="q6q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1856" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="30" /> <option name="api" value="30" />
<option name="brand" value="google" /> <option name="brand" value="google" />
@@ -466,17 +268,6 @@
<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" />
@@ -499,17 +290,6 @@
<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" />
@@ -533,15 +313,15 @@
<option name="screenY" value="2424" /> <option name="screenY" value="2424" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="35" /> <option name="api" value="29" />
<option name="brand" value="google" /> <option name="brand" value="samsung" />
<option name="codename" value="tokay" /> <option name="codename" value="x1q" />
<option name="id" value="tokay" /> <option name="id" value="x1q" />
<option name="manufacturer" value="Google" /> <option name="manufacturer" value="Samsung" />
<option name="name" value="Pixel 9" /> <option name="name" value="Galaxy S20" />
<option name="screenDensity" value="420" /> <option name="screenDensity" value="480" />
<option name="screenX" value="1080" /> <option name="screenX" value="1440" />
<option name="screenY" value="2424" /> <option name="screenY" value="3200" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
</list> </list>
</option> </option>

2
.idea/vcs.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

View File

@@ -1,7 +1,4 @@
plugins { plugins {
kotlin("kapt")
id("com.google.dagger.hilt.android")
id("com.google.gms.google-services")
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.jetbrains.kotlin.android)
} }
@@ -11,12 +8,11 @@ android {
compileSdk = 34 compileSdk = 34
defaultConfig { defaultConfig {
applicationId = "in.sminnovations.attendanceapp.user" applicationId = "in.sminnovations.attendanceapp"
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 11 versionCode = 1
versionName = "1.0"
versionName = "3.1.5"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@@ -26,12 +22,11 @@ android {
buildTypes { buildTypes {
release { release {
isMinifyEnabled = true isMinifyEnabled = false
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" "proguard-rules.pro"
) )
signingConfig = signingConfigs.getByName("debug")
} }
} }
compileOptions { compileOptions {
@@ -52,11 +47,6 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/META-INF/{AL2.0,LGPL2.1}"
} }
} }
// Allow references to generated code
kapt {
correctErrorTypes = true
}
} }
dependencies { dependencies {
@@ -69,8 +59,6 @@ dependencies {
implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
implementation(libs.androidx.datastore.core.android)
implementation(libs.androidx.datastore.preferences.core.jvm)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
@@ -78,34 +66,4 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4) androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest) debugImplementation(libs.androidx.ui.test.manifest)
implementation ("androidx.camera:camera-camera2:1.3.4")
implementation ( "androidx.camera:camera-lifecycle:1.3.4")
implementation ("androidx.camera:camera-view:1.3.4")
implementation ("com.google.mlkit:barcode-scanning:17.3.0")
implementation ("com.google.accompanist:accompanist-permissions:0.31.2-alpha")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6")
implementation ("androidx.navigation:navigation-compose:2.7.2")
implementation("androidx.datastore:datastore-preferences:1.1.1")
implementation("com.google.dagger:hilt-android:2.51.1")
kapt("com.google.dagger:hilt-android-compiler:2.51.1")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
val nav_version = "2.8.2"
implementation("androidx.navigation:navigation-compose:$nav_version")
// Import the Firebase BoM
implementation(platform("com.google.firebase:firebase-bom:33.4.0"))
// When using the BoM, you don't specify versions in Firebase library dependencies
// Add the dependency for the Firebase SDK for Google Analytics
implementation("com.google.firebase:firebase-analytics")
implementation ("com.google.firebase:firebase-firestore-ktx:25.1.0") // Use the latest version
implementation("com.google.firebase:firebase-firestore")
implementation ("com.squareup.okhttp3:okhttp:4.11.0")
} }

View File

@@ -1,48 +0,0 @@
{
"project_info": {
"project_number": "482294935697",
"project_id": "attendenceapp-57022",
"storage_bucket": "attendenceapp-57022.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:482294935697:android:07559f83f8f740be6c8f30",
"android_client_info": {
"package_name": "in.sminnovations.attendanceapp.admin"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyA0H9rJ8rML0JeBvN8rx0V2ijSA0GEfKFo"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:482294935697:android:89783c6f1e52abd76c8f30",
"android_client_info": {
"package_name": "in.sminnovations.attendanceapp.user"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyA0H9rJ8rML0JeBvN8rx0V2ijSA0GEfKFo"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@@ -1,22 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application <application
android:name=".main.MyApp" android:allowBackup="true"
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@@ -26,25 +13,16 @@
android:theme="@style/Theme.SMIAttendance" android:theme="@style/Theme.SMIAttendance"
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".main.MainAppActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.SMIAttendance"> android:theme="@style/Theme.SMIAttendance">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application> </application>
</manifest> </manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,26 @@
package `in`.sminnovations.attendanceapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import `in`.sminnovations.attendanceapp.ui.theme.SMIAttendanceTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
SMIAttendanceTheme {
}
}
}
}

View File

@@ -1,34 +0,0 @@
package `in`.sminnovations.attendanceapp.di
import android.content.Context
import com.google.firebase.firestore.FirebaseFirestore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import `in`.sminnovations.attendanceapp.utils.NetworkMonitor
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideAppContext(@ApplicationContext context: Context): Context {
return context
}
@Provides
@Singleton
fun provideFirebase(): FirebaseFirestore {
return FirebaseFirestore.getInstance()
}
@Provides
@Singleton
fun provideNetworkMonitor(@ApplicationContext context: Context): NetworkMonitor {
return NetworkMonitor(context)
}
}

View File

@@ -1,9 +0,0 @@
package `in`.sminnovations.attendanceapp.main
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApp : Application()

View File

@@ -1,42 +0,0 @@
package `in`.sminnovations.attendanceapp.main
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import dagger.hilt.android.AndroidEntryPoint
import `in`.sminnovations.attendanceapp.ui.theme.SMIAttendanceTheme
@AndroidEntryPoint
class MainAppActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (shouldKeepScreenOn()) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
enableEdgeToEdge()
setContent {
SMIAttendanceTheme {
MainScreen()
}
}
}
private fun shouldKeepScreenOn(): Boolean {
val batteryStatus = registerReceiver(
null,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
) ?: return false
val level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
val isCharging = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0
val batteryPct = level * 100 / scale
return batteryPct > 50 || isCharging
}
}

View File

@@ -1,21 +0,0 @@
package `in`.sminnovations.attendanceapp.main
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import `in`.sminnovations.attendanceapp.screens.home.ui.HomeScreen
@Composable
fun MainScreen(
) {
Scaffold(
content = { padding ->
Box(modifier = Modifier.padding(padding)) {
HomeScreen()
}
}
)
}

View File

@@ -1,14 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.data
data class AttendanceUiState(
val isScanning: Boolean = false,
val qrCodeValue: String? = null,
val userName: String? = null,
val isCheckedIn: Boolean = false,
val isCheckedOut: Boolean = false,
val checkedOutTime: String = "",
val errorMessage: String? = null,
val isLoading: Boolean = false,
val isConnected : Boolean =true,
val isAppDisabled : Boolean = false,
)

View File

@@ -1,86 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.isGranted
import `in`.sminnovations.attendanceapp.R
import `in`.sminnovations.attendanceapp.screens.home.data.AttendanceUiState
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.TopComponents
@OptIn(ExperimentalPermissionsApi::class)
@Composable
internal fun AttendanceScreenContent(
uiState: AttendanceUiState,
cameraPermissionState: PermissionState,
onScannerToggle: () -> Unit,
onClickSettings: () -> Unit,
onQrCodeScanned: (String) -> Unit,
modifier: Modifier = Modifier
) {
Box(modifier = modifier.fillMaxSize()) {
IconButton(onClick = onClickSettings) {
Icon(painter = painterResource(id = R.drawable.baseline_settings_24), contentDescription ="settings" )
}
Column {
when {
!cameraPermissionState.status.isGranted -> {
RequestPermissionsContent(cameraPermissionState)
}
uiState.isScanning -> {
ScannerContent(
onQrCodeScanned = onQrCodeScanned, onClose = onScannerToggle
)
}
uiState.errorMessage != null -> {
ErrorContent(
errorMessage = uiState.errorMessage,
userName = uiState.userName,
checkedOutTime = uiState.checkedOutTime
)
}
uiState.isCheckedIn -> {
AttendanceStatusCard(
title = "Checked In as", userName = uiState.userName
)
}
uiState.isCheckedOut -> {
AttendanceStatusCard(
title = "Checked Out as", userName = uiState.userName
)
}
}
}
if (uiState.isLoading) {
LoadingOverlay()
}
if (!uiState.isScanning && uiState.errorMessage == null && !uiState.isCheckedIn && !uiState.isCheckedOut && cameraPermissionState.status.isGranted){
Box(modifier =Modifier.align(Alignment.Center) ){
TopComponents()}
}
if (!uiState.isScanning && uiState.errorMessage == null && !uiState.isCheckedIn && !uiState.isCheckedOut && !cameraPermissionState.status.isGranted){
TopComponents()
}
ScannerToggleButton(
isScanning = uiState.isScanning,
onClick = onScannerToggle,
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}

View File

@@ -1,44 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
internal fun AttendanceStatusCard(
title: String, userName: String?
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
OutlinedCard {
Text(
text = title,
letterSpacing = 2.sp,
lineHeight = 40.sp,
fontSize = 30.sp,
color = Color.Green,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(10.dp, 5.dp)
)
}
Spacer(modifier = Modifier.height(10.dp))
Text(
text = userName ?: "",
letterSpacing = 2.sp,
lineHeight = 40.sp,
fontSize = 30.sp,
fontWeight = FontWeight.Bold
)
}
}

View File

@@ -1,131 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
internal fun ErrorContent(
errorMessage: String,
userName: String?,
checkedOutTime: String
) {
val errorExplanations = mapOf(
// Network and Data Access Errors
"Failed to fetch employee data" to "There was an issue retrieving your employee information. Please check your internet connection and try again.",
"Failed to fetch attendance data" to "Unable to retrieve attendance records. Please verify your internet connection and try again.",
// Employee Validation Errors
"Invalid employee ID" to "The scanned ID isn't recognized. Double-check the ID, and if the issue continues, contact your administrator.",
"Employee ID has expired" to "This ID is no longer active. Please contact HR to renew or reactivate your ID.",
// Attendance Process Errors
"Invalid attendance record" to "An unexpected issue occurred with your attendance record. Please contact support for assistance.",
"Already checked out for today." to "You've already completed your check-out for today. If you need to make corrections, please contact support.",
"Failed to check in" to "There was an issue recording your check-in. Please try again or contact support if the problem persists.",
"Failed to check out" to "There was an issue recording your check-out. Please try again or contact support if the problem persists.",
// Generic Error
"An error occurred" to "An unexpected error occurred. Please try again or contact support if the issue continues.",
//Quick Login Log off
"Wait 5 minutes before logging off" to "You have just Logged in You need to wait 5 minutes before logging in \n This behaviour may be notified to Admin 😊"
)
val detailedExplanation = errorExplanations[errorMessage] ?: "An unexpected error occurred."
Column(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Only show the "Error Message for" text if we have a valid userName
if (!userName.isNullOrEmpty()) {
OutlinedCard(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
) {
Text(
text = buildAnnotatedString {
append("Error Message for ")
withStyle(style = SpanStyle(color = Color.Green)) {
append(userName)
}
},
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(16.dp)
)
}
}
OutlinedCard(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
) {
Text(
text = errorMessage,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(16.dp)
)
}
// Detailed explanation for the error
OutlinedCard(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = if (checkedOutTime.isNotEmpty()) 16.dp else 0.dp)
) {
Text(
letterSpacing = 2.sp,
lineHeight = 40.sp,
text = detailedExplanation,
fontSize = 18.sp,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(16.dp)
)
}
if (checkedOutTime.isNotEmpty()) {
OutlinedCard(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Check Out Time",
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = checkedOutTime,
style = MaterialTheme.typography.titleLarge,
color = Color.Green
)
}
}
}
}
}

View File

@@ -1,30 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
internal fun LoadingOverlay() {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.3f)),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
color = MaterialTheme.colorScheme.primary, modifier = Modifier.size(48.dp)
)
}
}

View File

@@ -1,68 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.subSetting.AboutSettings
@Composable
fun NetworkErrorDialog(
showDialog: Boolean,
onRetry: () -> Unit,
onCheckForUpdate: () -> Unit
) {
if (!showDialog) return
var showSettingDialog by remember { mutableStateOf(false) }
val context = LocalContext.current
AlertDialog(
onDismissRequest = { /* Prevent dismissing by tapping outside */ },
title = {
Text(
text = if (showSettingDialog) "Settings" else "Network Error",
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
},
text = {
if (showSettingDialog) {
AboutSettings(context = context, onCheckForUpdate)
} else {
Text(
text = "Network is not connected. Please check your Wi-Fi or mobile data.\n\nIf Wi-Fi is just connected, wait for a while and then press retry.",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
modifier = Modifier.padding(vertical = 8.dp)
)
}
},
confirmButton = {
if (!showSettingDialog) {
Button(onClick = onRetry) {
Text("Retry")
}
}
},
dismissButton = {
Button(onClick = { showSettingDialog = !showSettingDialog }) {
Text(text = if (showSettingDialog) "Network" else "Settings")
}
},
modifier = Modifier.padding(16.dp)
)
}

View File

@@ -1,105 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import android.annotation.SuppressLint
import android.util.Log
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.*
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
@Composable
fun QRCodeScannerScreen(
onQRCodeScanned: (String) -> Unit,
lifecycleOwner: LifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
) {
OutlinedCard(
modifier = Modifier.padding(10.dp)
) {
val context = LocalContext.current
val previewView = remember { PreviewView(context) }
LaunchedEffect(Unit) {
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().apply {
setSurfaceProvider(previewView.surfaceProvider)
}
val barcodeScanner = BarcodeScanning.getClient(
BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
)
val imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also { analyzer ->
analyzer.setAnalyzer(ContextCompat.getMainExecutor(context)) { imageProxy ->
processImageProxy(imageProxy, barcodeScanner) { value ->
onQRCodeScanned(value)
}
}
}
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
CameraSelector.DEFAULT_FRONT_CAMERA,
preview,
imageAnalyzer
)
} catch (e: Exception) {
Log.e("QRCodeScanner", "Camera binding failed", e)
}
}
AndroidView(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
,
factory = { previewView }
)
}
}
@SuppressLint("UnsafeOptInUsageError")
private fun processImageProxy(
imageProxy: ImageProxy,
scanner: com.google.mlkit.vision.barcode.BarcodeScanner,
onQRCodeScanned: (String) -> Unit
) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
scanner.process(image)
.addOnSuccessListener { barcodes ->
barcodes.firstOrNull()?.rawValue?.let { qrCodeValue ->
onQRCodeScanned(qrCodeValue)
}
}
.addOnFailureListener {
Log.e("QRCodeScanner", "Barcode processing failed", it)
}
.addOnCompleteListener {
imageProxy.close()
}
} else {
imageProxy.close()
}
}

View File

@@ -1,35 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
internal fun ScannerContent(
onQrCodeScanned: (String) -> Unit, onClose: () -> Unit
) {
Box(modifier = Modifier.fillMaxSize()) {
QRCodeScannerScreen(onQrCodeScanned)
IconButton(
onClick = onClose, modifier = Modifier
.align(Alignment.TopStart)
.padding(16.dp)
) {
Icon(Icons.Default.ArrowBack, contentDescription = "Close Scanner")
}
}
BackHandler { onClose() }
}

View File

@@ -1,54 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun ScannerToggleButton(
isScanning: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier
) {
OutlinedCard(
onClick = onClick, colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primary
), modifier = modifier
.fillMaxWidth()
.padding(16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = if (isScanning) Icons.Default.Close else Icons.Default.KeyboardArrowUp,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp)
)
Text(
text = if (isScanning) "Close Scanner" else "Open Scanner",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
}
}
}

View File

@@ -1,126 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import `in`.sminnovations.attendanceapp.utils.UpdateInfo
@Composable
fun UpdateDialog(
updateInfo: UpdateInfo,
downloadProgress: Float,
onDismiss: () -> Unit,
onInstall: (String) -> Unit,
modifier: Modifier = Modifier
) {
val isinstallPressed = remember { mutableStateOf(false) } //not a smart approach but it works
AlertDialog(
onDismissRequest = {
// Only allow dismissing if not downloading
if (downloadProgress == 0f) onDismiss()
},
modifier = modifier,
title = {
Text(
text = "Update Available",
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
},
text = {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "A new version (${updateInfo.version}) is available. Please update.",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(vertical = 8.dp)
)
Text(
text = "Release Note:",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Start,
modifier = Modifier.padding(vertical = 8.dp),
fontWeight = FontWeight.Bold
)
Text(
text = updateInfo.releaseNote,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Start,
modifier = Modifier.padding(vertical = 8.dp),
fontWeight = FontWeight.SemiBold,
fontFamily = FontFamily.Monospace
)
if (downloadProgress > 0f) {
Spacer(modifier = Modifier.height(8.dp))
Row (verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center){
LinearProgressIndicator(progress = downloadProgress , modifier = Modifier.fillMaxWidth(0.8f))
Text(text = " ${floatToProgress(downloadProgress)}%")
}
}
}
},
confirmButton = {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
Button(
onClick = {
onInstall(updateInfo.url)
isinstallPressed.value = true },
enabled = downloadProgress == 0f || downloadProgress == 1f
) {
AnimatedVisibility(visible = isinstallPressed.value && downloadProgress ==0f) {
CircularProgressIndicator(color = MaterialTheme.colorScheme.surface)
}
Text(
text = if (downloadProgress > 0f && downloadProgress <1f) "Downloading..." else if (downloadProgress == 1f && isinstallPressed.value) "ReInstall Again" else "Install",
modifier = Modifier.padding(start = if(isinstallPressed.value && downloadProgress ==0f) 10.dp else 0.dp )
)
}
}
},
dismissButton = {
if (downloadProgress == 0f) {
Button(onClick = onDismiss) {
Text("Later")
}
}
}
)
}
fun floatToProgress(float: Float): Int{
return (float * 100f).toInt()
}

View File

@@ -1,42 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
@OptIn(ExperimentalPermissionsApi::class)
@Composable
internal fun RequestPermissionsContent(cameraPermissionState: PermissionState) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Camera Permission Required",
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
Text("Request Permission")
}
}
}

View File

@@ -1,91 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.ui
import android.Manifest
import android.content.Context
import android.widget.Toast
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import `in`.sminnovations.attendanceapp.screens.home.ui.componets.AttendanceScreenContent
import `in`.sminnovations.attendanceapp.screens.home.ui.componets.NetworkErrorDialog
import `in`.sminnovations.attendanceapp.screens.home.ui.componets.UpdateDialog
import `in`.sminnovations.attendanceapp.screens.home.viewModel.HomeViewModel
import `in`.sminnovations.attendanceapp.screens.settings.ui.SettingsScreenDialogue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun HomeScreen(
viewModel: HomeViewModel = hiltViewModel(),
) {
val uiState by viewModel.uiState.collectAsState()
val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
val updateInfo by viewModel.updateInfo.collectAsState()
val isUpdateDialogVisible by viewModel.isUpdateDialogVisible.collectAsState()
val showSettingDialog = remember { mutableStateOf(false) }
val downloadProgress by viewModel.downloadProgress.collectAsState()
val context = LocalContext.current
Box(modifier = Modifier.fillMaxSize()) {
fun checkForUpdateToast(context: Context ,value : Boolean){
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
delay(1000)
}
if (value) {
Toast.makeText(context, "App is Upto Date", Toast.LENGTH_SHORT).show()
}
}
SettingsScreenDialogue(showDialogue = showSettingDialog.value,
onDismissDialogue = { showSettingDialog.value = false },
onCheckForUpdate = {
viewModel.checkForUpdates(context)
checkForUpdateToast(context,!isUpdateDialogVisible)
})
if (uiState.isConnected) {
viewModel.checkForUpdates(context)
if (isUpdateDialogVisible && updateInfo != null) {
UpdateDialog(updateInfo = updateInfo!!,
downloadProgress = downloadProgress,
onDismiss = { viewModel.dismissUpdate() },
onInstall = { url -> viewModel.installUpdate(url) })
}
AttendanceScreenContent(uiState = uiState,
cameraPermissionState = cameraPermissionState,
onScannerToggle = { if (uiState.isScanning) viewModel.stopScanning() else viewModel.startScanning() },
onQrCodeScanned = viewModel::setQRCodeValue,
onClickSettings = { showSettingDialog.value = true })
} else {
NetworkErrorDialog(showDialog = true, onRetry = {
viewModel.reStartMonitoring()
}, onCheckForUpdate = {
viewModel.checkForUpdates(context)
checkForUpdateToast(context,!isUpdateDialogVisible)
})
}
}
}

View File

@@ -1,412 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.home.viewModel
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope
import com.google.firebase.Timestamp
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FirebaseFirestore
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.NetworkMonitor
import `in`.sminnovations.attendanceapp.utils.UpdateChecker
import `in`.sminnovations.attendanceapp.utils.UpdateInfo
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import java.text.SimpleDateFormat
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.Date
import java.util.Locale
import javax.inject.Inject
import kotlin.math.abs
const val timeFormat = "dd MMMM yyyy hh:mm:ss a"
@HiltViewModel
class HomeViewModel @Inject constructor(
applicationContext: Context,
private val firebaseFirestore: FirebaseFirestore,
private val networkMonitor: NetworkMonitor
) : ViewModel() {
private val _uiState = MutableStateFlow(AttendanceUiState())
val uiState: StateFlow<AttendanceUiState> = _uiState.asStateFlow()
private val _updateInfo = MutableStateFlow<UpdateInfo?>(null)
val updateInfo = _updateInfo.asStateFlow()
private val _isUpdateDialogVisible = MutableStateFlow(false)
val isUpdateDialogVisible = _isUpdateDialogVisible.asStateFlow()
private val _downloadProgress = MutableStateFlow(0f)
val downloadProgress: StateFlow<Float> = _downloadProgress
private val appUpdater = AppUpdater(applicationContext)
fun checkForUpdates(context: Context) {
viewModelScope.launch {
UpdateChecker.checkForUpdate(context).onSuccess { updateInfo ->
if (updateInfo.isNew) {
_updateInfo.value = updateInfo
_isUpdateDialogVisible.value = true
}
}.onFailure { _ ->
// Handle error
}
}
}
fun installUpdate(url: String) {
// Start the download
appUpdater.downloadAndInstall(url)
viewModelScope.launch {
appUpdater.updateState.collect { state ->
when (state) {
is AppUpdater.UpdaterState.Downloading -> {
_downloadProgress.value = state.progress
}
is AppUpdater.UpdaterState.Success -> {
_downloadProgress.value = 1f
}
is AppUpdater.UpdaterState.Error -> {
_downloadProgress.value = 0f
}
AppUpdater.UpdaterState.Idle -> {
// No op
}
}
}
}
}
fun dismissUpdate() {
_isUpdateDialogVisible.value = false
_updateInfo.value = null
_downloadProgress.value = 0f
}
init {
networkMonitor.startMonitoring()
viewModelScope.launch {
networkMonitor.isConnected.asFlow().collectLatest { isConnected ->
_uiState.update { it.copy(isConnected = isConnected) }
if (isConnected) {
startScanning()
}
}
}
}
fun reStartMonitoring() {
networkMonitor.startMonitoring()
}
fun startScanning() {
viewModelScope.launch {
_uiState.update { _ ->
AttendanceUiState( // Reset to completely fresh state
isScanning = true
)
}
}
}
fun stopScanning() {
viewModelScope.launch {
_uiState.update { currentState ->
currentState.copy(
isScanning = false, errorMessage = null, qrCodeValue = null
)
}
}
}
fun setQRCodeValue(value: String) {
viewModelScope.launch {
_uiState.update {
it.copy(
qrCodeValue = value,
userName = null, // Reset userName when new QR code is scanned
errorMessage = null
)
}
stopScanning()
processQRCode(value)
}
}
private fun processQRCode(userId: String) {
if (userId.isBlank()) return
viewModelScope.launch {
try {
setLoading(true)
_uiState.update {
it.copy(errorMessage = null, userName = null) // Reset UI state
}
// Fetch Employee Document
val employeeDoc = getEmployeeDocument(userId)
delay(200L)
if (employeeDoc == null) {
handleError("Invalid employee ID")
return@launch
}
if (employeeDoc.getBoolean("EmployeeStatus") != true) {
_uiState.update {
it.copy(
userName = employeeDoc.getString("Name"),
errorMessage = "Employee ID has expired"
)
}
return@launch
}
_uiState.update { it.copy(userName = employeeDoc.getString("Name")) }
// Fetch Attendance Document
val currentDate = getCurrentFormattedDate()
val attendanceDoc = getAttendanceDocument(userId, currentDate)
if (attendanceDoc?.exists() == true) {
handleExistingAttendance(attendanceDoc)
_uiState.update { it.copy(isCheckedOut = true, qrCodeValue = null) }
} else {
handleNewAttendance(userId, currentDate)
}
} catch (e: Exception) {
handleError("An error occurred: ${e.message}")
} finally {
setLoading(false)
}
}
}
private fun handleError(message: String) {
viewModelScope.launch {
_uiState.update {
it.copy(
errorMessage = message,
isLoading = false,
userName = null // Reset userName on error
)
}
}
}
private suspend fun getEmployeeDocument(userId: String): DocumentSnapshot? {
return try {
val querySnapshot =
firebaseFirestore.collection("employees").whereEqualTo("Id", userId).get().await()
if (querySnapshot.isEmpty) null else querySnapshot.documents.first()
} catch (e: Exception) {
throw Exception("Failed to fetch employee data")
}
}
private suspend fun getAttendanceDocument(userId: String, date: String): DocumentSnapshot? {
return try {
firebaseFirestore.collection("attendance_$userId").document(date).get().await()
} catch (e: Exception) {
throw Exception("Failed to fetch attendance data")
}
}
private fun setLoading(loading: Boolean) {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = loading) }
}
}
private fun getCurrentFormattedDate(): String {
return SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(Date())
}
private fun formatTimestamp(timestamp: Timestamp): String {
return SimpleDateFormat(
timeFormat, Locale.getDefault()
).format(timestamp.toDate())
}
// fun isAppDisabled(): Boolean {
// val a = firebaseFirestore.collection("deviceState").document("state")
// println(a)
// return true
// }
private suspend fun handleExistingAttendance(document: DocumentSnapshot) {
val data = document.data ?: return
val checkIns = data.filterKeys { it.startsWith("checkIn") }.size
val checkOuts = data.filterKeys { it.startsWith("checkOut") }.size
delay(200L)
val currentTimestamp = formatTimestamp(Timestamp.now())
val checkIn = document.getString("checkIn$checkIns") ?: run {
handleError("Missing check-in data")
return
}
if (isTimeDifferenceGreaterThanSec(givenTimeStr = checkIn, currentTimeStr = currentTimestamp, secVal = 5L)) {
if (!isTimeDifferenceGreaterThanMinutes(givenTimeStr = checkIn, currentTimeStr = currentTimestamp, minutesVal = 5L)) {
handleError("Wait 5 minutes before logging off")
return
} else {
if (checkIns > checkOuts) {
processCheckOut(document, checkIns)
} else {
processCheckIn(document, checkIns + 1)
}
}
}else{
return
}
}
private fun isTimeDifferenceGreaterThanMinutes(
givenTimeStr: String,
currentTimeStr: String = formatTimestamp(Timestamp.now()),
minutesVal: Long
): Boolean {
val formatter = DateTimeFormatter.ofPattern(timeFormat)
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) {
try {
val checkInData = hashMapOf(
"DayCompleted" to false,
"checkIn1" to formatTimestamp(Timestamp.now()),
"totalWork" to "00:00:00"
)
firebaseFirestore.collection("attendance_$userId").document(currentDate)
.set(checkInData).await()
_uiState.update {
it.copy(
isCheckedIn = true, qrCodeValue = null
)
}
} catch (e: Exception) {
handleError("Failed to check in: ${e.message}")
}
}
private suspend fun processCheckIn(document: DocumentSnapshot, checkInCount: Int) {
try {
document.reference.update("checkIn$checkInCount", formatTimestamp(Timestamp.now())).await()
_uiState.update {
it.copy(
isCheckedIn = true, qrCodeValue = null
)
}
} catch (e: Exception) {
handleError("Failed to check in: ${e.message}")
}
}
private suspend fun processCheckOut(document: DocumentSnapshot, checkInCount: Int) {
try {
val checkOutTime = formatTimestamp(Timestamp.now())
document.reference.update("checkOut$checkInCount", checkOutTime).await()
val updatedDocument = document.reference.get().await()
val totalWork = calculateTotalWork(updatedDocument)
document.reference.update("totalWork", totalWork.first).await()
document.reference.update("DayCompleted", totalWork.second).await()
_uiState.update {
it.copy(
isCheckedOut = true, qrCodeValue = null
)
}
} catch (e: Exception) {
handleError("Failed to check out: ${e.message}")
}
}
private fun calculateTotalWork(document: DocumentSnapshot): Pair<String, Boolean> {
val formatter = DateTimeFormatter.ofPattern(timeFormat, Locale.getDefault())
var totalMinutes = 0L
for (i in 1..20) {
val checkIn = document.getString("checkIn$i") ?: break
val checkOut = document.getString("checkOut$i") ?: continue
val checkInTime = LocalDateTime.parse(checkIn, formatter)
val checkOutTime = LocalDateTime.parse(checkOut, formatter)
totalMinutes += ChronoUnit.MINUTES.between(checkInTime, checkOutTime)
}
val hours = totalMinutes / 60
val minutes = totalMinutes % 60
val isDayCompleted = totalMinutes >= 420L
return Pair(String.format("%02d:%02d:00", hours, minutes), isDayCompleted)
}
override fun onCleared() {
super.onCleared()
networkMonitor.stopMonitoring()
}
}

View File

@@ -1,78 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.settings.data
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class SettingsData(private val context: Context) {
companion object {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore("Settings")
val DYNAMIC_THEME_KEY = booleanPreferencesKey("dynamic_theme")
val BLACK_THEME_KEY = booleanPreferencesKey("black_theme")
val THEME_MODE_KEY = stringPreferencesKey("app_theme")
val DEV_MODE_TOGGLE_KEY = booleanPreferencesKey("dev_mode_toggle")
}
val getDevModeToggle: Flow<Boolean?> = context.dataStore.data
.map { preferences ->
preferences[DEV_MODE_TOGGLE_KEY] ?: false
}
suspend fun setDevMode(mode: Boolean) {
context.dataStore.edit { preferences ->
preferences[DEV_MODE_TOGGLE_KEY] = mode
}
}
val getDynamicColorThemeValue: Flow<Boolean?> = context.dataStore.data
.map { preferences ->
preferences[DYNAMIC_THEME_KEY] ?: true
}
suspend fun setDynamicColorThemeValue(value: Boolean) {
context.dataStore.edit { preferences ->
preferences[DYNAMIC_THEME_KEY] = value
}
}
val getBlackThemeValue: Flow<Boolean?> = context.dataStore.data
.map { preferences ->
preferences[BLACK_THEME_KEY] ?: false
}
suspend fun setBlackThemeValue(value: Boolean) {
context.dataStore.edit { preferences ->
preferences[BLACK_THEME_KEY] = value
}
}
val getThemeMode: Flow<String> = context.dataStore.data
.map { preferences ->
preferences[THEME_MODE_KEY] ?: "System default"
}
suspend fun saveThemeMode(theme: String) {
context.dataStore.edit { preferences ->
preferences[THEME_MODE_KEY] = theme
}
}
}

View File

@@ -1,41 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.subSetting.AboutSettings
@Composable
fun SettingsScreenDialogue(
showDialogue: Boolean,
onDismissDialogue: () -> Unit,
onCheckForUpdate: () -> Unit,
) {
if (showDialogue) {
val context = LocalContext.current
AlertDialog(
onDismissRequest = { /* Do nothing to prevent dismissal by tapping outside */ },
title = {
Text(text = "Settings")
},
text = {
AboutSettings(context = context,onCheckForUpdate)
},
confirmButton = {},
dismissButton = {
Button(onClick = onDismissDialogue) {
Text("Close")
}
},
modifier = Modifier.padding(16.dp)
)
}
}

View File

@@ -1,41 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import `in`.sminnovations.attendanceapp.R
@Composable
fun TopComponents(
modifier: Modifier = Modifier
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Image(
painter = painterResource(id = R.drawable.smi_logo),
contentDescription = "Profile Photo",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(150.dp)
.clip(RoundedCornerShape(16.dp))
)
Spacer(modifier = Modifier.height(16.dp))
}
}

View File

@@ -1,165 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@Composable
fun ClickableCard(
title:String,
subtitle:String,
onClick: (Boolean) -> Unit,
icon: ImageVector
) {
Row(
modifier = Modifier
// .background(MaterialTheme.colorScheme.surfaceContainer)
.clickable{ onClick(true)}
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column(
modifier = Modifier
.weight(1f)
) {
Text(
text = title,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = subtitle,
style = MaterialTheme.typography.bodyMedium,
)
}
}
}
@Composable
fun ClickableCardNonBoolean(
title:String,
subtitle:String,
onClick: () -> Unit,
icon: ImageVector
) {
Row(
modifier = Modifier
// .background(MaterialTheme.colorScheme.surfaceContainer)
.clickable{ onClick()}
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column(
modifier = Modifier
.weight(1f)
) {
Text(
text = title,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = subtitle,
style = MaterialTheme.typography.bodyMedium,
)
}
}
}
@OptIn( ExperimentalFoundationApi::class)
@Composable
fun CombinedClickableCard(
title:String,
subtitle:String,
onClick: (Boolean) -> Unit,
onDoubleClick: ((Boolean) -> Unit?)? =null,
onLongClick: ((Boolean) -> Unit?)? =null,
icon: ImageVector
) {
val interactionSource = remember { MutableInteractionSource() }
Row(
modifier = Modifier
// .background(MaterialTheme.colorScheme.surfaceContainer)
.combinedClickable (
onClick = { onClick(true) },
onDoubleClick = {
if (onDoubleClick != null) {
onDoubleClick(true)
}
},
onLongClick = {
if (onLongClick != null) {
onLongClick(true)
}
}
)
.fillMaxWidth()
.padding(16.dp)
.indication(interactionSource,null)
,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column(
modifier = Modifier
.weight(1f)
) {
Text(
text = title,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = subtitle,
style = MaterialTheme.typography.bodyMedium,
)
}
}
}

View File

@@ -1,71 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@Composable
fun SwitchCard(
checkedValue:Boolean,
title:String,
subtitle:String,
onSwitchChange: (Boolean) -> Unit,
icon: ImageVector
) {
Row(
modifier = Modifier
.fillMaxWidth()
// .background(MaterialTheme.colorScheme.surfaceContainer)
.toggleable(
value = checkedValue,
enabled = true,
role = Role.Switch,
onValueChange = onSwitchChange
)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = title,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.bodyLarge
)
Text(
text = subtitle,
style = MaterialTheme.typography.bodyMedium,
)
}
Switch(
checked = checkedValue,
onCheckedChange = onSwitchChange
)
}
}

View File

@@ -1,51 +0,0 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui.components.subSetting
import android.content.Context
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import `in`.sminnovations.attendanceapp.R
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.ClickableCard
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.ClickableCardNonBoolean
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.CombinedClickableCard
@Composable
fun AboutSettings(
context: Context ,
onCheckUpdate : () -> Unit
) {
Column() {
OutlinedCard {
ClickableCard(
title = "Version",
subtitle = context.packageManager.getPackageInfo(
context.packageName,
0
).versionName,
onClick = {},
icon = ImageVector.vectorResource(id = R.drawable.round_info_24)
)
ClickableCardNonBoolean(
title = "Check For Update",
subtitle = "Check For latest update",
onClick = onCheckUpdate,
icon = ImageVector.vectorResource(id = R.drawable.round_info_24)
)
CombinedClickableCard(title = "Developer",
subtitle = "ShanMukha Innovations Pvt Ltd",
onClick = {},
onDoubleClick = {
},
icon = ImageVector.vectorResource(id = R.drawable.baseline_developer_mode_24)
)
}
}
}

View File

@@ -2,11 +2,10 @@ package `in`.sminnovations.attendanceapp.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFFF9800) val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFFFCB7E) val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFF9B6D7B) val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF000000)
val PurpleGrey40 = Color(0xFF865307)
val Pink40 = Color(0xFF7D5260)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@@ -11,7 +11,7 @@ val Typography = Typography(
bodyLarge = TextStyle( bodyLarge = TextStyle(
fontFamily = FontFamily.Default, fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
fontSize = 18.sp, fontSize = 16.sp,
lineHeight = 24.sp, lineHeight = 24.sp,
letterSpacing = 0.5.sp letterSpacing = 0.5.sp
) )

View File

@@ -1,147 +0,0 @@
package `in`.sminnovations.attendanceapp.utils
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.content.FileProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.ResponseBody
import okio.Buffer
import okio.BufferedSource
import okio.ForwardingSource
import okio.Source
import okio.buffer
import java.io.File
import java.io.IOException
data class UpdateInfo(
val version: String = "",
val url: String = "",
val isNew: Boolean = false,
val releaseNote : String = ""
)
class AppUpdater(private val context: Context) {
sealed class UpdaterState {
data object Idle : UpdaterState()
data class Downloading(val progress: Float) : UpdaterState()
data class Success(val file: File) : UpdaterState()
data class Error(val message: String) : UpdaterState()
}
private val _updateState = MutableStateFlow<UpdaterState>(UpdaterState.Idle)
val updateState = _updateState.asStateFlow()
fun downloadAndInstall(url: String) {
CoroutineScope(Dispatchers.IO).launch {
try {
//Added for future use case
// Resolve the URL (e.g. for Google Drive links)
val resolvedUrl = resolveDownloadUrl(url)
val file = downloadApk(resolvedUrl) { progress ->
// Update progress on Main thread
CoroutineScope(Dispatchers.Main).launch {
_updateState.value = UpdaterState.Downloading(progress)
}
}
_updateState.value = UpdaterState.Success(file)
installApk(file)
} catch (e: Exception) {
_updateState.value = UpdaterState.Error(e.message ?: "Unknown error")
}
}
}
private fun resolveDownloadUrl(url: String): String {
// Optional
// If the URL is from Google Drive, extract file id and convert to direct download URL.
return if (url.contains("drive.google.com")) {
val regex = Regex("(?<=/d/)[^/]+")
val fileId = regex.find(url)?.value ?: Uri.parse(url).getQueryParameter("id")
if (!fileId.isNullOrEmpty())
"https://drive.google.com/uc?export=download&id=$fileId"
else url
} else {
url
}
}
private suspend fun downloadApk(url: String, onProgress: (Float) -> Unit): File =
withContext(Dispatchers.IO) {
val client = OkHttpClient.Builder()
.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
val contentLength = originalResponse.body?.contentLength() ?: -1L
originalResponse.newBuilder()
.body(originalResponse.body?.let { body ->
ProgressResponseBody(body) { progress ->
onProgress(progress)
}
})
.build()
}
.build()
val request = Request.Builder().url(url).build()
val file = File(context.cacheDir, "update.apk")
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Failed to download APK")
response.body?.let { body ->
file.outputStream().use { fileOut ->
body.byteStream().use { it.copyTo(fileOut) }
}
} ?: throw IOException("Empty response")
}
file
}
private fun installApk(file: File) {
try {
val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
if (file.length() == 0L) throw IOException("Downloaded file is empty")
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "application/vnd.android.package-archive")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
} else {
throw IOException("No app found to handle installation")
}
} catch (e: Exception) {
_updateState.value = UpdaterState.Error("Installation failed: ${e.message}")
}
}
}
private class ProgressResponseBody(
private val responseBody: ResponseBody,
private val onProgressUpdate: (Float) -> Unit
) : ResponseBody() {
private val bufferedSource: BufferedSource by lazy {
source(responseBody.source()).buffer()
}
override fun contentType() = responseBody.contentType()
override fun contentLength() = responseBody.contentLength()
override fun source(): BufferedSource = bufferedSource
private fun source(source: Source): Source =
object : ForwardingSource(source) {
var totalBytesRead = 0L
override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount)
totalBytesRead += if (bytesRead != -1L) bytesRead else 0
val progress = if (contentLength() > 0) totalBytesRead.toFloat() / contentLength() else -1f
onProgressUpdate(progress)
return bytesRead
}
}
}

View File

@@ -1,78 +0,0 @@
package `in`.sminnovations.attendanceapp.utils
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import java.net.HttpURLConnection
import java.net.URL
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NetworkMonitor @Inject constructor(
@ApplicationContext private val context: Context
) {
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private val _isConnected = MutableLiveData(false)
val isConnected: LiveData<Boolean> get() = _isConnected
private val networkScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
performPingCheck()
}
override fun onLost(network: Network) {
_isConnected.postValue(false)
}
}
fun startMonitoring() {
val network = connectivityManager.activeNetwork
if (network != null) {
performPingCheck()
}
val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
}
fun stopMonitoring() {
try {
connectivityManager.unregisterNetworkCallback(networkCallback)
} catch (_: IllegalArgumentException) {
}
networkScope.cancel()
}
private fun performPingCheck() {
networkScope.launch {
try {
val url = URL("https://www.google.com")
val connection = url.openConnection() as HttpURLConnection
connection.connectTimeout = 2000
connection.connect()
val isReachable = connection.responseCode == 200
_isConnected.postValue(isReachable)
connection.disconnect()
} catch (e: Exception) {
_isConnected.postValue(false)
}
}
}
}

View File

@@ -1,83 +0,0 @@
package `in`.sminnovations.attendanceapp.utils
import android.content.Context
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase
import kotlinx.coroutines.tasks.await
object UpdateChecker {
private const val COLLECTION_NAME = "app_updates"
private const val DOCUMENT_NAME = "latest_version"
suspend fun checkForUpdate(
context: Context
): Result<UpdateInfo> = runCatching {
val currentVersion = getCurrentVersion(context)
val updateInfo = getLatestVersion()
if (isNewerVersion(currentVersion, updateInfo.version)) {
updateInfo.copy(isNew = true)
} else {
updateInfo.copy(isNew = false)
}
}
private fun getCurrentVersion(context: Context): String {
return try {
context.packageManager.getPackageInfo(context.packageName, 0).versionName
} catch (e: Exception) {
throw UpdateCheckException("Failed to get current app version", e)
}
}
private suspend fun getLatestVersion(): UpdateInfo {
return try {
val document = Firebase.firestore
.collection(COLLECTION_NAME)
.document(DOCUMENT_NAME)
.get()
.await()
if (!document.exists()) {
throw UpdateCheckException("Update document does not exist")
}
UpdateInfo(
version = document.getString("version") ?: throw UpdateCheckException("Version field missing"),
url = document.getString("url") ?: throw UpdateCheckException("URL field missing"),
releaseNote = document.getString("ReleaseNote") ?: throw UpdateCheckException("ReleaseNote field missing")
)
} catch (e: Exception) {
throw UpdateCheckException("Failed to fetch update info", e)
}
}
private fun isNewerVersion(currentVersion: String, latestVersion: String): Boolean {
return try {
val current = currentVersion.split(".").map { it.toIntOrNull() ?: 0 }
val latest = latestVersion.split(".").map { it.toIntOrNull() ?: 0 }
for (i in 0 until maxOf(current.size, latest.size)) {
val currentNum = current.getOrNull(i) ?: 0
val latestNum = latest.getOrNull(i) ?: 0
when {
latestNum > currentNum -> return true
latestNum < currentNum -> return false
}
}
false
} catch (e: Exception) {
false
}
}
}
class UpdateCheckException(
message: String,
cause: Throwable? = null
) : Exception(message, cause){
}

View File

@@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M7,5h10v2h2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v4h2L7,5zM15.41,16.59L20,12l-4.59,-4.59L14,8.83 17.17,12 14,15.17l1.41,1.42zM10,15.17L6.83,12 10,8.83 8.59,7.41 4,12l4.59,4.59L10,15.17zM17,19L7,19v-2L5,17v4c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2v2z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M16,16h2c0.55,0 1,0.45 1,1s-0.45,1 -1,1h-2c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1zM16,8h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1h-5c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1zM16,12h4c0.55,0 1,0.45 1,1s-0.45,1 -1,1h-4c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1zM3,18c0,1.1 0.9,2 2,2h6c1.1,0 2,-0.9 2,-2L13,8L3,8v10zM13,5h-2l-0.71,-0.71c-0.18,-0.18 -0.44,-0.29 -0.7,-0.29L6.41,4c-0.26,0 -0.52,0.11 -0.7,0.29L5,5L3,5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h10c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,17c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v4c0,0.55 -0.45,1 -1,1zM13,9h-2L11,7h2v2z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#EBFFF3</color>
</resources>

View File

@@ -1,14 +1,3 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<string name="app_name">SMI Attendance</string> <string name="app_name">SMI Attendance</string>
<string name="cancel" tools:ignore="ButtonCase">CANCEL</string>
<string name="dynamic_theme">Dynamic Theme</string>
<string name="developer_options">Developer Options</string>
<string name="ok">OK</string>
<string name="stats">Stats</string>
<string name="theme_based_on_wallpaper_color">Theme based on wallpaper color</string>
<string name="theme">Theme</string>
<string name="appearance">Appearance</string>
<string name="select_theme_mode">Select Theme Mode</string>
<string name="black_theme">Black Theme</string>
<string name="black_theme_description">Pitch Black Theme</string>
</resources> </resources>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="updates"
path="." />
</paths>

View File

@@ -1,14 +1,8 @@
package `in`.sminnovations.attendanceapp package `in`.sminnovations.attendanceapp
import com.google.firebase.Timestamp
import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
import java.text.SimpleDateFormat
import java.time.LocalDateTime import org.junit.Assert.*
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.Locale
import kotlin.math.abs
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).
@@ -16,48 +10,8 @@ import kotlin.math.abs
* See [testing documentation](http://d.android.com/tools/testing). * See [testing documentation](http://d.android.com/tools/testing).
*/ */
class ExampleUnitTest { class ExampleUnitTest {
/*
@Test @Test
fun addition_isCorrect() { fun addition_isCorrect() {
assertEquals(4, 2 + 2) assertEquals(4, 2 + 2)
} }
@Test
fun timeTest(){
val currentTime = "30 January 2025 10:30:55 am"
val givenTime = "30 January 2025 10:31:01 am"
val minutes = 7L
val result = isTimeDifferenceGreaterThanSec(givenTime, currentTime, minutes)
println(result)
assertEquals(false,result)
}
//
// private fun isTimeDifferenceGreaterThanMinutes(givenTimeStr: String, currentTimeStr: String, minutes: Long): 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.MINUTES.between(givenTime, currentTime))
// 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())
}
*/
} }

View File

@@ -2,6 +2,4 @@
plugins { plugins {
alias(libs.plugins.android.application) apply false alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false alias(libs.plugins.jetbrains.kotlin.android) apply false
id("com.google.dagger.hilt.android") version "2.51.1" apply false }
id("com.google.gms.google-services") version "4.4.2" apply false
}

View File

@@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.5.0" agp = "8.4.0"
kotlin = "1.9.0" kotlin = "1.9.0"
coreKtx = "1.13.1" coreKtx = "1.13.1"
junit = "4.13.2" junit = "4.13.2"
@@ -8,8 +8,6 @@ espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.6" lifecycleRuntimeKtx = "2.8.6"
activityCompose = "1.9.2" activityCompose = "1.9.2"
composeBom = "2023.08.00" composeBom = "2023.08.00"
datastoreCoreAndroid = "1.1.1"
datastorePreferencesCoreJvm = "1.1.1"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -26,8 +24,6 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-datastore-core-android = { group = "androidx.datastore", name = "datastore-core-android", version.ref = "datastoreCoreAndroid" }
androidx-datastore-preferences-core-jvm = { group = "androidx.datastore", name = "datastore-preferences-core-jvm", version.ref = "datastorePreferencesCoreJvm" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }

View File

@@ -1,6 +1,6 @@
#Fri Oct 04 14:44:43 IST 2024 #Fri Oct 04 14:44:43 IST 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -16,10 +16,8 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
} }
rootProject.name = "SMI Attendance" rootProject.name = "SMI Attendance"
include(":app") include(":app")