75 Commits

Author SHA1 Message Date
sathwikcs
0447cc5b60 added status check for the login 2024-10-22 17:03:46 +05:30
sathwikcs
f578e537e0 Improved the login dataStore 2024-10-22 17:02:56 +05:30
sathwikcs
3f0f10e2c6 fixed so many issues regarding the qr state and check in checkout added date remember state and optimised code 2024-10-22 17:01:35 +05:30
sathwikcs
527de78877 fixed so many issues regarding the qr state and check in checkout added date remember state and optimised code 2024-10-22 17:01:30 +05:30
sathwikcs
18bcfb7549 added the refresh feature to history screen viewmodel 2024-10-22 16:58:55 +05:30
sathwikcs
18cd04ed92 added the refresh feature to history screen 2024-10-22 16:58:43 +05:30
sathwikcs
5d8fa4cb54 Updated the repo and made the the persistent in datastore 2024-10-22 16:58:16 +05:30
sathwikcs
96048ee619 Changed the Manifest 2024-10-22 16:56:48 +05:30
sathwikcs
05495f2e60 optimised 2024-10-21 22:16:38 +05:30
sathwikcs
d41f15925f deploymentTargetSelector.xml 2024-10-19 11:52:02 +05:30
sathwikcs
54c309d4f6 home 2024-10-19 11:51:45 +05:30
sathwikcs
fb3029e138 SettingsViewModel 2024-10-19 11:50:10 +05:30
sathwikcs
a5a314f39f CheckInCheckOutDataStore 2024-10-19 11:49:55 +05:30
sathwikcs
8187c0b2dc history 2024-10-19 11:49:21 +05:30
sathwikcs
d87fa7de80 Attendance.kt 2024-10-19 11:47:53 +05:30
sathwikcs
41fca7375e Main screen 2024-10-19 11:47:29 +05:30
sathwikcs
6777faefef App module 2024-10-19 11:47:18 +05:30
sathwikcs
7c23d1b493 About 2024-10-19 11:46:42 +05:30
sathwikcs
dfea713a26 Qr screen 2024-10-19 11:46:21 +05:30
sathwikcs
4c2e12f075 home screen 2024-10-19 11:46:03 +05:30
sathwikcs
39f43596ae other 2024-10-18 12:22:13 +05:30
sathwikcs
b23eb10f04 EmployeePreferencesKeys 2024-10-18 12:22:02 +05:30
sathwikcs
17eb17a9d9 gradle 2024-10-18 12:21:49 +05:30
sathwikcs
f27cddc464 app module 2024-10-18 12:21:38 +05:30
sathwikcs
4d3440fbac login 2024-10-18 12:21:11 +05:30
sathwikcs
81597efd5c datastore 2024-10-18 12:21:01 +05:30
sathwikcs
7bcc899eb9 Attendance 2024-10-18 12:20:46 +05:30
sathwikcs
d0ca0f88b8 Manifest update 2024-10-18 12:20:28 +05:30
sathwikcs
a4482ee342 Navigation 2024-10-18 12:20:16 +05:30
sathwikcs
498bb1a38e settings 2024-10-18 12:19:57 +05:30
sathwikcs
a46ba87d44 history 2024-10-18 12:19:38 +05:30
sathwikcs
b24e22b6d7 settings 2024-10-18 12:19:11 +05:30
sathwikcs
0a44e7eb92 Navigation 2024-10-18 12:18:44 +05:30
sathwikcs
61fc5132b2 Main screen 2024-10-18 12:18:33 +05:30
sathwikcs
032f007be6 Repo 2024-10-18 12:17:47 +05:30
sathwikcs
d926526440 Json 2024-10-18 12:17:37 +05:30
sathwikcs
737de0785c Settings 2024-10-18 12:17:28 +05:30
sathwikcs
8add42f1de others 2024-10-16 17:54:01 +05:30
sathwikcs
f8e67f1a7e RequestPermissions 2024-10-16 17:53:05 +05:30
sathwikcs
234b595e1f OutlinedTextFieldWithError 2024-10-16 17:52:48 +05:30
sathwikcs
a9e548db83 datastore.kt 2024-10-16 17:52:37 +05:30
sathwikcs
e82aa38917 LargeButton 2024-10-16 17:52:25 +05:30
sathwikcs
ef2ecefea9 HistoryScreen 2024-10-16 17:52:15 +05:30
sathwikcs
14aefcf3ae developer 2024-10-16 17:52:03 +05:30
sathwikcs
9f4c4c7057 Navigation 2024-10-16 17:50:49 +05:30
sathwikcs
bbbbb460ad Employee 2024-10-16 17:50:04 +05:30
sathwikcs
0595e791a1 Navigation 2024-10-16 17:49:42 +05:30
sathwikcs
81e4351695 home 2024-10-16 17:49:23 +05:30
sathwikcs
394074e0ce removed side Navigation 2024-10-16 17:48:13 +05:30
sathwikcs
3971b58472 attendence 2024-10-16 17:47:27 +05:30
sathwikcs
7b3e4465bd app Module 2024-10-16 17:47:02 +05:30
sathwikcs
c2de7bdd11 app json file 2024-10-16 17:46:20 +05:30
sathwikcs
42ac1fc8c4 qr scanner 2024-10-16 17:45:56 +05:30
sathwikcs
dcee750197 settings screen 2024-10-16 17:45:20 +05:30
sathwikcs
1ecaeb5fc1 settings screen 2024-10-16 17:42:05 +05:30
sathwikcs
ed354c7799 LoginScreen 2024-10-16 17:38:20 +05:30
sathwikcs
6ccab6e184 Settings added 2024-10-16 17:36:19 +05:30
sathwikcs
5246c8be89 gradle 2024-10-16 17:35:17 +05:30
sathwikcs
43f75b454f new strings.xml 2024-10-15 18:41:04 +05:30
sathwikcs
1fcab8ae14 new icons 2024-10-15 18:40:33 +05:30
sathwikcs
fa54dcba1c login screen vm , qr detect ml lit implemented ,hilt implemented ,data store implemented ,side navigation implemented 2024-10-15 16:15:20 +05:30
sathwikcs
6018fd73d3 baselines 2024-10-15 13:17:55 +05:30
sathwikcs
a7aff3c680 login screen 2024-10-10 18:27:35 +05:30
sathwikcs
2372dd9589 NavigationDrawer rearrange 2024-10-07 18:08:37 +05:30
sathwikcs
adf1a80ae8 NavigationDrawer rearrange 2024-10-07 18:07:41 +05:30
sathwikcs
679fa0ef69 NavigationDrawer 2024-10-07 17:51:41 +05:30
sathwikcs
b6d1133da2 Merge branch 'dev' of https://gitlab.com/sminnovations/digitalattendanceapp into dev 2024-10-07 13:32:47 +05:30
chandrashekhar reddy
ad3d6f974e button added in main 1 2024-10-07 13:29:18 +05:30
sathwikcs
29edb0f115 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	app/src/main/java/in/sminnovations/attendanceapp/MainActivity.kt
2024-10-07 13:26:14 +05:30
sathwikcs
d16a6b9c44 button 2024-10-07 13:23:40 +05:30
chandrashekhar reddy
251f91128a button added in main 2024-10-07 13:22:50 +05:30
sathwikcs
cf64155a41 skeleton framework 2024-10-07 13:07:52 +05:30
sathwikcs
8524a1b56e initial 2024-10-07 13:00:58 +05:30
Chandrashekhar Reddy
9cafedc9a0 Update .gitlab-ci.yml file 2024-10-04 10:17:43 +00:00
Chandrashekhar Reddy
0a889bdbea Update .gitlab-ci.yml file 2024-10-04 10:14:33 +00:00
67 changed files with 3309 additions and 39 deletions

101
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,101 @@
# 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,6 +4,20 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-10-16T06:25:13.029795100Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\sathw\.android\avd\Pixel_3a_XL_API_34.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
<SelectionState runConfigName="lofpre">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="checkTime">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>

340
.idea/other.xml generated Normal file
View File

@@ -0,0 +1,340 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="direct_access_persist.xml">
<option name="deviceSelectionList">
<list>
<PersistentDeviceSelectionData>
<option name="api" value="27" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="F01L" />
<option name="id" value="F01L" />
<option name="manufacturer" value="FUJITSU" />
<option name="name" value="F-01L" />
<option name="screenDensity" value="360" />
<option name="screenX" value="720" />
<option name="screenY" value="1280" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="28" />
<option name="brand" value="DOCOMO" />
<option name="codename" value="SH-01L" />
<option name="id" value="SH-01L" />
<option name="manufacturer" value="SHARP" />
<option name="name" value="AQUOS sense2 SH-01L" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="Lenovo" />
<option name="codename" value="TB370FU" />
<option name="id" value="TB370FU" />
<option name="manufacturer" value="Lenovo" />
<option name="name" value="Tab P12" />
<option name="screenDensity" value="340" />
<option name="screenX" value="1840" />
<option name="screenY" value="2944" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="samsung" />
<option name="codename" value="a51" />
<option name="id" value="a51" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A51" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="akita" />
<option name="id" value="akita" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="b0q" />
<option name="id" value="b0q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S22 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="32" />
<option name="brand" value="google" />
<option name="codename" value="bluejay" />
<option name="id" value="bluejay" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="caiman" />
<option name="id" value="caiman" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro" />
<option name="screenDensity" value="360" />
<option name="screenX" value="960" />
<option name="screenY" value="2142" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="comet" />
<option name="id" value="comet" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro Fold" />
<option name="screenDensity" value="390" />
<option name="screenX" value="2076" />
<option name="screenY" value="2152" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="crownqlteue" />
<option name="id" value="crownqlteue" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Note9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2220" />
<option name="screenY" value="1080" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="dm3q" />
<option name="id" value="dm3q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S23 Ultra" />
<option name="screenDensity" value="600" />
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="e1q" />
<option name="id" value="e1q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S24" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="felix" />
<option name="id" value="felix" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="felix_camera" />
<option name="id" value="felix_camera" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Fold (Camera-enabled)" />
<option name="screenDensity" value="420" />
<option name="screenX" value="2208" />
<option name="screenY" value="1840" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="gts8uwifi" />
<option name="id" value="gts8uwifi" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Tab S8 Ultra" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1848" />
<option name="screenY" value="2960" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="husky" />
<option name="id" value="husky" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8 Pro" />
<option name="screenDensity" value="390" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="motorola" />
<option name="codename" value="java" />
<option name="id" value="java" />
<option name="manufacturer" value="Motorola" />
<option name="name" value="G20" />
<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="google" />
<option name="codename" value="komodo" />
<option name="id" value="komodo" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9 Pro XL" />
<option name="screenDensity" value="360" />
<option name="screenX" value="1008" />
<option name="screenY" value="2244" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="lynx" />
<option name="id" value="lynx" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7a" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="31" />
<option name="brand" value="google" />
<option name="codename" value="oriole" />
<option name="id" value="oriole" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 6" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="panther" />
<option name="id" value="panther" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 7" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />
<option name="codename" value="q5q" />
<option name="id" value="q5q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Fold5" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1812" />
<option name="screenY" value="2176" />
</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="SM-F956B" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1856" />
<option name="screenY" value="2160" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="r11" />
<option name="id" value="r11" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Watch" />
<option name="screenDensity" value="320" />
<option name="screenX" value="384" />
<option name="screenY" value="384" />
<option name="type" value="WEAR_OS" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="redfin" />
<option name="id" value="redfin" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 5" />
<option name="screenDensity" value="440" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="shiba" />
<option name="id" value="shiba" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 8" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2400" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="google" />
<option name="codename" value="tangorpro" />
<option name="id" value="tangorpro" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel Tablet" />
<option name="screenDensity" value="320" />
<option name="screenX" value="1600" />
<option name="screenY" value="2560" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="id" value="tokay" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="29" />
<option name="brand" value="samsung" />
<option name="codename" value="x1q" />
<option name="id" value="x1q" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S20" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1440" />
<option name="screenY" value="3200" />
</PersistentDeviceSelectionData>
</list>
</option>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

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

View File

@@ -1,6 +1,10 @@
plugins {
kotlin("kapt")
id("com.google.dagger.hilt.android")
id("com.google.gms.google-services")
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
}
android {
@@ -8,7 +12,7 @@ android {
compileSdk = 34
defaultConfig {
applicationId = "in.sminnovations.attendanceapp"
applicationId = "in.sminnovations.attendanceapp.user"
minSdk = 26
targetSdk = 34
versionCode = 1
@@ -47,6 +51,10 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
// Allow references to generated code
kapt {
correctErrorTypes = true
}
}
dependencies {
@@ -59,6 +67,8 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.datastore.core.android)
implementation(libs.androidx.datastore.preferences.core.jvm)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
@@ -66,4 +76,31 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
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")
}

48
app/google-services.json Normal file
View File

@@ -0,0 +1,48 @@
{
"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,9 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
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.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application
android:allowBackup="true"
android:name=".MyApp"
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
@@ -15,11 +21,9 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.SMIAttendance">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

View File

@@ -0,0 +1,13 @@
package `in`.sminnovations.attendanceapp
// File: MyApp.kt
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApp : Application(){
}

View File

@@ -0,0 +1,41 @@
package `in`.sminnovations.attendanceapp
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import `in`.sminnovations.attendanceapp.screens.login.ui.LoginScreen
@Composable
fun AppNavigation() {
val navController = rememberNavController()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
NavigationHost(navController = navController)
}
}
@RequiresApi(Build.VERSION_CODES.O_MR1)
@Composable
fun NavigationHost(navController: NavHostController) {
NavHost(navController = navController, startDestination = "login") {
composable("login") {
LoginScreen(
loginViewModel = hiltViewModel(),
onLoginSuccess = {
navController.navigate("main") {
popUpTo("login") { inclusive = true }
}
}
)
}
composable("main") {
MainScreen(navController)
}
}
}

View File

@@ -4,44 +4,19 @@ 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 dagger.hilt.android.AndroidEntryPoint
import `in`.sminnovations.attendanceapp.ui.theme.SMIAttendanceTheme
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
SMIAttendanceTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
AppNavigation()
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
SMIAttendanceTheme {
Greeting("Android")
}
}

View File

@@ -0,0 +1,30 @@
package `in`.sminnovations.attendanceapp
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 androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import `in`.sminnovations.attendanceapp.navigation.BottomNavigationBar
import `in`.sminnovations.attendanceapp.navigation.Navigation
import `in`.sminnovations.attendanceapp.navigation.data.navItems
@Composable
fun MainScreen(
mainNavController: NavController
) {
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigationBar(navController, navItems)
},
content = { padding ->
Box(modifier = Modifier.padding(padding)) {
Navigation(navController = navController, mainNavHostController = mainNavController, items = navItems)
}
}
)
}

View File

@@ -0,0 +1,14 @@
package `in`.sminnovations.attendanceapp.data.dataStore
data class Employee(
val androidID: String = "",
val dateOfJoining: String = "",
val dateOfLeaving: String = "",
val designation: String = "Android Developer",
val employeeStatus: Boolean = true,
val id: String = "",
val name: String = "",
val password: String = "",
val photo: String = "",
val dateOfBirth: String = ""
)

View File

@@ -0,0 +1,17 @@
package `in`.sminnovations.attendanceapp.data.dataStore
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
object EmployeePreferencesKeys {
val ANDROID_ID = stringPreferencesKey("android_id")
val DATE_OF_JOINING = stringPreferencesKey("date_of_joining")
val DATE_OF_LEAVING = stringPreferencesKey("date_of_leaving")
val DESIGNATION = stringPreferencesKey("designation")
val EMPLOYEE_STATUS = booleanPreferencesKey("employee_status")
val ID = stringPreferencesKey("id")
val NAME = stringPreferencesKey("name")
val PASSWORD = stringPreferencesKey("password")
val PHOTO = stringPreferencesKey("photo")
val DATE_OF_BIRTH = stringPreferencesKey("date_of_birth")
}

View File

@@ -0,0 +1,83 @@
package `in`.sminnovations.attendanceapp.data.dataStore
import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import `in`.sminnovations.attendanceapp.screens.settings.data.EmployeeDataSettings
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import java.io.IOException
import javax.inject.Inject
import javax.inject.Singleton
val Context.employeeDataStore by preferencesDataStore("employee_data")
@Singleton
class EmployeeRepository @Inject constructor(
@ApplicationContext private val context: Context
) {
suspend fun saveEmployeeData(employee: Employee) {
context.employeeDataStore.edit { preferences ->
preferences[EmployeePreferencesKeys.ANDROID_ID] = employee.androidID
preferences[EmployeePreferencesKeys.DATE_OF_JOINING] = employee.dateOfJoining
preferences[EmployeePreferencesKeys.DATE_OF_LEAVING] = employee.dateOfLeaving
preferences[EmployeePreferencesKeys.DESIGNATION] = employee.designation
preferences[EmployeePreferencesKeys.EMPLOYEE_STATUS] = employee.employeeStatus
preferences[EmployeePreferencesKeys.ID] = employee.id
preferences[EmployeePreferencesKeys.NAME] = employee.name
preferences[EmployeePreferencesKeys.PASSWORD] = employee.password
preferences[EmployeePreferencesKeys.PHOTO] = employee.photo
preferences[EmployeePreferencesKeys.DATE_OF_BIRTH] = employee.dateOfBirth
}
}
fun getEmployeeData(): Flow<Employee> {
return context.employeeDataStore.data
.catch { exception ->
// If an error occurs, emit an empty preferences object
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
Employee(
androidID = preferences[EmployeePreferencesKeys.ANDROID_ID] ?: "",
dateOfJoining = preferences[EmployeePreferencesKeys.DATE_OF_JOINING] ?: "",
dateOfLeaving = preferences[EmployeePreferencesKeys.DATE_OF_LEAVING] ?: "",
designation = preferences[EmployeePreferencesKeys.DESIGNATION] ?: "Android Developer",
employeeStatus = preferences[EmployeePreferencesKeys.EMPLOYEE_STATUS] ?: true,
id = preferences[EmployeePreferencesKeys.ID] ?: "",
name = preferences[EmployeePreferencesKeys.NAME] ?: "",
password = preferences[EmployeePreferencesKeys.PASSWORD] ?: "",
photo = preferences[EmployeePreferencesKeys.PHOTO] ?: "",
dateOfBirth = preferences[EmployeePreferencesKeys.DATE_OF_BIRTH] ?: ""
)
}
}
fun getEmployeeDataSettings(): Flow<EmployeeDataSettings> {
return context.employeeDataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception
}
}
.map { preferences ->
EmployeeDataSettings(
name = preferences[EmployeePreferencesKeys.NAME] ?: "",
id = preferences[EmployeePreferencesKeys.ID] ?: "",
designation = preferences[EmployeePreferencesKeys.DESIGNATION] ?: ""
)
}
}
}

View File

@@ -0,0 +1,57 @@
package `in`.sminnovations.attendanceapp.di
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
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.screens.home.data.CheckInCheckOutDataStore
import `in`.sminnovations.attendanceapp.screens.login.data.LoginDataStore
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
private val Context.dataStore by preferencesDataStore(name = "employee_data")
@Provides
@Singleton
fun provideAppContext(@ApplicationContext context: Context): Context {
return context
}
@Provides
@Singleton
fun provideLoginDataStore(@ApplicationContext context: Context): LoginDataStore {
return LoginDataStore(context)
}
@Provides
@Singleton
fun provideCheckInCheckOutDataStore(@ApplicationContext context: Context): CheckInCheckOutDataStore {
return CheckInCheckOutDataStore(context)
}
@Provides
@Singleton
fun provideFirebase(): FirebaseFirestore {
return FirebaseFirestore.getInstance()
}
@Provides
@Singleton
fun provideEmployeeDataStore(@ApplicationContext context: Context): DataStore<Preferences> {
return context.dataStore
}
// fun provideFirebase(): FirebaseFirestore {
// return FirebaseFirestore.getInstance()
// }
}

View File

@@ -0,0 +1,48 @@
package `in`.sminnovations.attendanceapp.navigation
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import `in`.sminnovations.attendanceapp.navigation.data.BottomNavItem
@Composable
fun BottomNavigationBar(
navController: NavController,
items: List<BottomNavItem>,
) {
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { item ->
NavigationBarItem(icon = {
BottomNavAnimatedIcon(
isSelected = currentRoute == item.route,
icon = item.icon,
selectedIcon = item.selectedIcon,
contentDescription = item.label
)
},
label = { Text(text = item.label) },
alwaysShowLabel = true,
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let { route ->
popUpTo(route) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
})
}
}
}

View File

@@ -0,0 +1,33 @@
package `in`.sminnovations.attendanceapp.navigation
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
@Composable
fun BottomNavAnimatedIcon(
isSelected: Boolean,
icon: ImageVector,
selectedIcon: ImageVector,
contentDescription: String?
) {
AnimatedContent(
targetState = isSelected,
transitionSpec = {
fadeIn(animationSpec = tween(300)) togetherWith fadeOut(animationSpec = tween(300))
}, label = "animated icon"
) { targetState ->
Icon(
imageVector = if (targetState) selectedIcon else icon,
contentDescription = contentDescription,
tint = if (targetState) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.onSurface
)
}
}

View File

@@ -0,0 +1,33 @@
package `in`.sminnovations.attendanceapp.navigation
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import `in`.sminnovations.attendanceapp.navigation.data.BottomNavItem
import `in`.sminnovations.attendanceapp.screens.history.ui.HistoryScreen
import `in`.sminnovations.attendanceapp.screens.home.ui.HomeScreen
import `in`.sminnovations.attendanceapp.screens.settings.ui.SettingsScreen
@Composable
fun Navigation(
mainNavHostController: NavController,
navController: NavHostController,
items: List<BottomNavItem>,
) {
NavHost(navController, startDestination = items[0].route) {
composable(items[0].route) {
HomeScreen()
}
composable(items[1].route) {
HistoryScreen()
}
composable(items[2].route) {
SettingsScreen(mainNavHostController)
}
}
}

View File

@@ -0,0 +1,23 @@
package `in`.sminnovations.attendanceapp.navigation.data
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddCircle
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.outlined.AddCircle
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.ui.graphics.vector.ImageVector
data class BottomNavItem(
val icon: ImageVector,
val selectedIcon: ImageVector,
val label: String,
val route: String
)
val navItems = listOf(
BottomNavItem(Icons.Outlined.Search, Icons.Filled.Search, "Scan", "scan"),
BottomNavItem(Icons.Outlined.AddCircle, Icons.Filled.AddCircle, "Records", "records"),
BottomNavItem(Icons.Outlined.Settings, Icons.Filled.Settings, "Settings", "settings")
)

View File

@@ -0,0 +1,73 @@
package `in`.sminnovations.attendanceapp.screens.history.components
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.*
import androidx.compose.runtime.*
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
import `in`.sminnovations.attendanceapp.screens.history.data.AttendanceData
@Composable
fun EnhancedAttendanceCard(attendance: AttendanceData) {
var isExpanded by remember { mutableStateOf(false) }
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp)
.animateContentSize(), // Animate the size change for a smooth expand/collapse
shape = RoundedCornerShape(12.dp),
elevation = CardDefaults.cardElevation(6.dp)
) {
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(16.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = attendance.date,
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
color = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.weight(1f)
)
IconButton(
onClick = { isExpanded = !isExpanded }
) {
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = "Expand or Collapse",
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
if (isExpanded) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Check In: ${attendance.checkInTime ?: "N/A"}",
color = MaterialTheme.colorScheme.onPrimaryContainer
)
Text(
text = "Check Out: ${attendance.checkOutTime ?: "N/A"}",
color = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
}
}

View File

@@ -0,0 +1,9 @@
package `in`.sminnovations.attendanceapp.screens.history.data
import com.google.firebase.Timestamp
data class AttendanceData(
val date: String,
val checkInTime: String?,
val checkOutTime: String?
)

View File

@@ -0,0 +1,62 @@
package `in`.sminnovations.attendanceapp.screens.history.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import `in`.sminnovations.attendanceapp.screens.history.components.EnhancedAttendanceCard
import `in`.sminnovations.attendanceapp.screens.history.viewModel.HistoryViewModel
import `in`.sminnovations.attendanceapp.screens.login.viewModel.LoginViewModel
@Composable
fun HistoryScreen(
historyViewModel: HistoryViewModel = hiltViewModel(),
) {
val attendanceData = historyViewModel.listOfAttendanceDates.collectAsState().value
Column(modifier = Modifier.fillMaxSize()) {
Row (
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
){
Text(
text = "Your Attendance History",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(16.dp),
color = MaterialTheme.colorScheme.primary
)
Button(onClick = {historyViewModel.refresh() }) {
Text(text = "Refresh")
}
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 8.dp)
) {
items(attendanceData) { attendance ->
EnhancedAttendanceCard(attendance = attendance)
}
}
}
}

View File

@@ -0,0 +1,97 @@
package `in`.sminnovations.attendanceapp.screens.history.viewModel
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.firebase.Timestamp
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.toObject
import dagger.hilt.android.lifecycle.HiltViewModel
import `in`.sminnovations.attendanceapp.screens.history.data.AttendanceData
import `in`.sminnovations.attendanceapp.screens.login.data.LoginDataStore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import javax.inject.Inject
@HiltViewModel
class HistoryViewModel @Inject constructor(
private val loginDataStore: LoginDataStore,
private val firebaseFirestore: FirebaseFirestore,
) : ViewModel() {
private val _isLoggedIn = MutableStateFlow(false)
val isLoggedIn = _isLoggedIn.asStateFlow()
private val _listOfAttendanceDates = MutableStateFlow<List<AttendanceData>>(emptyList())
val listOfAttendanceDates = _listOfAttendanceDates.asStateFlow()
init {
viewModelScope.launch {
getAttendanceData()
}
}
fun refresh() {
viewModelScope.launch {
getAttendanceData()
}
}
private fun getAttendanceData() {
viewModelScope.launch {
loginDataStore.loginId.collect { id ->
firebaseFirestore.collection("attendance_$id").get()
.addOnSuccessListener { result ->
viewModelScope.launch {
val attendanceList = result.documents.mapNotNull { document ->
val date = document.id // Assuming the document ID is the date
val checkInTimestamp = document.getString("checkIn") ?: "NA"
val checkOutTimestamp = document.getString("checkOut") ?: "NA"
val checkIn = formatTimestamp(checkInTimestamp)
val checkOut = formatTimestamp(checkOutTimestamp)
AttendanceData(date, checkIn, checkOut)
}
_listOfAttendanceDates.value = attendanceList
}
}
.addOnFailureListener { exception ->
Log.e("Firestore", "Error getting documents: ", exception)
viewModelScope.launch {
_listOfAttendanceDates.value = emptyList()
}
}
}
}
}
private fun formatTimestamp(timestamp: Any): String {
val sdf = SimpleDateFormat("dd MMMM yyyy hh:mm a", Locale.getDefault())
return if (timestamp is Timestamp) {
sdf.format(timestamp.toDate())
} else {
timestamp.toString()
}
}
}

View File

@@ -0,0 +1,74 @@
package `in`.sminnovations.attendanceapp.screens.home.data
import android.content.Context
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton
private const val DATASTORE_NAME = "check_in_check_out_prefs"
private val Context.dataStore by preferencesDataStore(name = DATASTORE_NAME)
@Singleton
class CheckInCheckOutDataStore @Inject constructor(@ApplicationContext context: Context) {
private val dataStore = context.dataStore
private val CHECK_IN_KEY = stringPreferencesKey("check_in_id")
private val CHECK_OUT_KEY = stringPreferencesKey("check_out_id")
private val CHECK_IN_Date = stringPreferencesKey("check_in_date_id")
private val IS_CHECK_IN = booleanPreferencesKey("is_checked_in_id")
private val IS_CHECK_OUT = booleanPreferencesKey("is_checked_in_id")
val checkInId: Flow<String> = dataStore.data.map { preferences ->
preferences[CHECK_IN_KEY] ?: ""
}
val checkOutId: Flow<String> = dataStore.data.map { preferences ->
preferences[CHECK_OUT_KEY] ?: ""
}
val checkInDate: Flow<String> = dataStore.data.map { preferences ->
preferences[CHECK_IN_Date] ?: ""
}
suspend fun saveLoginData(checkOut: String) {
dataStore.edit { preferences ->
preferences[CHECK_OUT_KEY] = checkOut
}
}
suspend fun saveLoginDate(checkDate: String,checkInTime: String) {
dataStore.edit { preferences ->
preferences[CHECK_IN_Date] = checkDate
preferences[CHECK_IN_KEY] = checkInTime
}
}
suspend fun saveIsLogIn(isCheckIn: Boolean) {
dataStore.edit { preferences ->
preferences[IS_CHECK_IN] = isCheckIn
}
}
suspend fun clearLoginData() {
dataStore.edit { preferences ->
preferences.remove(CHECK_IN_Date)
preferences.remove(IS_CHECK_OUT)
preferences.remove(IS_CHECK_IN)
preferences.remove(CHECK_IN_KEY)
preferences.remove(CHECK_OUT_KEY)
}
}
}

View File

@@ -0,0 +1,2 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets

View File

@@ -0,0 +1,40 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun LargeButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Button(
onClick = onClick,
modifier = modifier
.height(56.dp)
.width(200.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
),
shape = RoundedCornerShape(28.dp)
) {
Text(
text = text,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
}
}

View File

@@ -0,0 +1,37 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.shouldShowRationale
@Composable
fun DefaultScreen(
qrCodeValue: String?,
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
qrCodeValue?.let {
Text(text = "Scanned Value: $it", modifier = Modifier.padding(16.dp))
}
}
}

View File

@@ -0,0 +1,106 @@
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.platform.LocalLifecycleOwner
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_BACK_CAMERA,
preview,
imageAnalyzer
)
} catch (e: Exception) {
Log.e("QRCodeScanner", "Camera binding failed", e)
}
}
AndroidView(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f),
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

@@ -0,0 +1,27 @@
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.isGranted
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun RequestPermissions(
cameraPermissionState: PermissionState,
locationPermissionState: PermissionState
) {
LaunchedEffect(cameraPermissionState.status.isGranted, locationPermissionState.status.isGranted) {
when {
!cameraPermissionState.status.isGranted -> {
cameraPermissionState.launchPermissionRequest()
}
!locationPermissionState.status.isGranted -> {
locationPermissionState.launchPermissionRequest()
}
}
}
}

View File

@@ -0,0 +1,217 @@
package `in`.sminnovations.attendanceapp.screens.home.ui
import android.Manifest
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import `in`.sminnovations.attendanceapp.screens.home.ui.componets.DefaultScreen
import `in`.sminnovations.attendanceapp.screens.home.ui.componets.LargeButton
import `in`.sminnovations.attendanceapp.screens.home.ui.componets.QRCodeScannerScreen
import `in`.sminnovations.attendanceapp.screens.home.ui.componets.RequestPermissions
import `in`.sminnovations.attendanceapp.screens.home.viewModel.HomeViewModel
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {
val isScanning by viewModel.isScanning.collectAsState()
val qrCodeValue by viewModel.qrCodeValue.collectAsState()
val checkInTime by viewModel.checkedIn.collectAsState()
val checkOutTime by viewModel.checkedOut.collectAsState()
val isCheckedIn by viewModel.isCheckedIn.collectAsState()
val isCheckedOut by viewModel.isCheckedOut.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
val errorMessage by viewModel.errorMessage.collectAsState()
val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA)
val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
val allPermissionsGranted = remember {
derivedStateOf {
cameraPermissionState.status.isGranted && locationPermissionState.status.isGranted
}
}
Box(modifier = Modifier.fillMaxSize()) {
if (!allPermissionsGranted.value) {
RequestPermissions(
cameraPermissionState = cameraPermissionState,
locationPermissionState = locationPermissionState
)
} else {
DefaultScreen(qrCodeValue = qrCodeValue)
AnimatedVisibility(
visible = isScanning,
enter = expandVertically(expandFrom = Alignment.Bottom) + fadeIn(),
exit = shrinkVertically(shrinkTowards = Alignment.Bottom) + fadeOut()
) {
Box(modifier = Modifier.fillMaxSize()) {
QRCodeScannerScreen(viewModel::setQRCodeValue)
IconButton(
onClick = { viewModel.stopScanning() },
modifier = Modifier
.align(Alignment.TopStart)
.padding(16.dp)
) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
}
}
}
// Loading Indicator
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
}
// Display Error Message
errorMessage?.let { message ->
Box(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.padding(16.dp)
) {
Text(
text = message,
color = MaterialTheme.colorScheme.error,
fontWeight = FontWeight.Bold,
modifier = Modifier
.background(Color(0xFFFFCDD2)) // Light red background for error
.padding(8.dp)
.align(Alignment.Center)
)
}
}
OutlinedCard(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(.47f)
.align(Alignment.BottomCenter)
.padding(10.dp),
onClick = { }
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.padding(20.dp)
.align(Alignment.TopCenter)
.fillMaxWidth()
) {
errorMessage?.let { message ->
Text(
text = message,
color = MaterialTheme.colorScheme.error,
fontWeight = FontWeight.Bold,
modifier = Modifier
.background(Color(0xFFFFCDD2)) // Light red background for error
.padding(8.dp)
)
}
AnimatedVisibility(visible = isCheckedIn && !isCheckedOut) {
Text(
text = "You already checked in. Scanning again will Check out.",
letterSpacing = 2.sp,
lineHeight = 40.sp,
fontSize = 30.sp,
color = MaterialTheme.colorScheme.error,
fontWeight = FontWeight.Bold
)
}
AnimatedVisibility(visible = !isCheckedIn && !isCheckedOut) {
Text(
text = "Please Check In",
letterSpacing = 2.sp,
lineHeight = 40.sp,
fontSize = 30.sp,
color = Color.Green,
fontWeight = FontWeight.Bold
)
}
Column(Modifier.padding(15.dp)) {
AnimatedVisibility(visible = isCheckedIn || isCheckedOut) {
Text(text = "Check-in: $checkInTime")
Spacer(modifier = Modifier.height(10.dp))
}
AnimatedVisibility(visible = (isCheckedIn && isCheckedOut) || isCheckedOut ) {
Text(text = "Check-out: $checkOutTime")
Spacer(modifier = Modifier.height(10.dp))
}
}
}
Column(Modifier.align(Alignment.BottomCenter)) {
LargeButton(
text = if (isScanning) "Close" else "Scan",
onClick = if (isScanning) {
{ viewModel.stopScanning() }
} else {
{ viewModel.startScanning() }
},
modifier = Modifier.padding(bottom = 24.dp)
)
if (isScanning) {
BackHandler { viewModel.stopScanning() }
}
AnimatedVisibility(visible = !isCheckedIn) {
LargeButton(
text = "Check In",
onClick = { viewModel.loginNow() },
modifier = Modifier.padding(bottom = 24.dp)
)
}
AnimatedVisibility(visible = isCheckedIn ) {
LargeButton(
text = "Check Out",
onClick = { viewModel.loginNow() },
modifier = Modifier.padding(bottom = 24.dp)
)
}
if (isScanning) {
BackHandler { viewModel.stopScanning() }
}
}
}
}
}
}

View File

@@ -0,0 +1,256 @@
package `in`.sminnovations.attendanceapp.screens.home.viewModel
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.firebase.Timestamp
import com.google.firebase.firestore.FirebaseFirestore
import dagger.hilt.android.lifecycle.HiltViewModel
import `in`.sminnovations.attendanceapp.screens.home.data.CheckInCheckOutDataStore
import `in`.sminnovations.attendanceapp.screens.login.data.LoginDataStore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.util.Date
import java.util.Locale
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor(
private val loginDataStore: LoginDataStore,
private val checkInCheckOutDataStore: CheckInCheckOutDataStore,
private val firebaseFirestore: FirebaseFirestore,
) : ViewModel() {
private val _qrCodeValue = MutableStateFlow<String?>(null)
val qrCodeValue: StateFlow<String?> = _qrCodeValue.asStateFlow()
private val _isScanning = MutableStateFlow(false)
val isScanning: StateFlow<Boolean> = _isScanning.asStateFlow()
private val _checkedIn = MutableStateFlow("")
val checkedIn: StateFlow<String> = _checkedIn.asStateFlow()
private val _checkedOut = MutableStateFlow("N/A")
val checkedOut: StateFlow<String> = _checkedOut.asStateFlow()
private val _isCheckedIn = MutableStateFlow(false)
val isCheckedIn: StateFlow<Boolean> = _isCheckedIn.asStateFlow()
private val _isCheckedOut = MutableStateFlow(false)
val isCheckedOut: StateFlow<Boolean> = _isCheckedOut.asStateFlow()
private val _errorMessage = MutableStateFlow<String?>(null)
val errorMessage: StateFlow<String?> = _errorMessage.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private fun postErrorMessage(message: String) {
viewModelScope.launch {
_errorMessage.emit(message)
}
}
fun startScanning() {
_isScanning.value = true
}
fun stopScanning() {
_isScanning.value = false
}
fun setQRCodeValue(value: String) {
viewModelScope.launch {
_qrCodeValue.emit(value)
stopScanning()
}
}
private fun formatTimestamp(timestamp: Timestamp): String {
val sdf =
SimpleDateFormat("dd MMMM yyyy hh:mm:ss a", Locale.getDefault()) // Format with AM/PM
return sdf.format(timestamp.toDate())
}
init {
viewModelScope.launch {
getCheckInChekout()
}
}
private fun getCheckInChekout() {
viewModelScope.launch {
checkInCheckOutDataStore.checkInId.collect { checkIn ->
_checkedIn.value = checkIn
}
}
viewModelScope.launch {
checkInCheckOutDataStore.checkOutId.collect { checkOut ->
_checkedOut.value = checkOut
}
}
viewModelScope.launch {
checkInCheckOutDataStore.checkInDate.collect {date ->
if (date == LocalDate.now().toString()) {
_isCheckedIn.value = true
}
}
}
}
private fun getFormattedDate(): String {
val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault())
return dateFormat.format(Date())
}
fun loginNow() {
viewModelScope.launch {
_isLoading.value = true
checkInCheckOutDataStore.checkInDate.collect { checkOutDate ->
loginDataStore.loginId.collect { _ ->
firebaseFirestore.collection("qr_key").whereEqualTo("id", getFormattedDate())
.get().addOnSuccessListener { querySnapshot ->
_isLoading.value = false
if (!querySnapshot.isEmpty) {
for (document in querySnapshot) {
if (qrCodeValue.value == document.getString("key")) {
viewModelScope.launch {
if (checkOutDate != LocalDate.now().toString() || checkOutDate == ""
) {
putCheckInAttendance("")
} else {
viewModelScope.launch {
_isCheckedIn.value = true
}
putCheckOutAttendance()
}
}
} else {
postErrorMessage("Invalid QR code scanned")
}
}
} else {
postErrorMessage("No matching QR key found for today's date")
}
}.addOnFailureListener { exception ->
_isLoading.value = false
postErrorMessage("Error fetching QR key: ${exception.message}")
}
}
}
}
}
private fun putCheckInAttendance(location: String) {
viewModelScope.launch {
_isLoading.value = true
loginDataStore.loginId.collect { id ->
val currentDate = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(Date())
val attendanceRef =
firebaseFirestore.collection("attendance_$id").document(currentDate)
attendanceRef.get().addOnSuccessListener { document ->
_isLoading.value = false
if (!document.exists()) {
val checkInData = hashMapOf(
"Status" to false,
"checkIn" to formatTimestamp(Timestamp.now()),
"location" to location,
"checkOut" to "NA"
)
attendanceRef.set(checkInData).addOnSuccessListener {
viewModelScope.launch {
checkInCheckOutDataStore.saveLoginDate(
checkDate = LocalDate.now().toString(),
checkInTime = formatTimestamp(Timestamp.now())
)
}
Log.d("Firestore", "Check-in successful")
viewModelScope.launch {
_isCheckedIn.value = true
}
}.addOnFailureListener { e ->
postErrorMessage("Error adding check-in: ${e.message}")
}
} else {
postErrorMessage("Check-in already exists for today. Cannot check in again.")
viewModelScope.launch {
_isCheckedIn.value = true
}
}
}.addOnFailureListener { exception ->
_isLoading.value = false
postErrorMessage("Error fetching document: ${exception.message}")
}
}
}
}
private fun putCheckOutAttendance() {
viewModelScope.launch {
_isLoading.value = true
loginDataStore.loginId.collect { id ->
val currentDate = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(Date())
val attendanceRef =
firebaseFirestore.collection("attendance_$id").document(currentDate)
attendanceRef.get().addOnSuccessListener { document ->
_isLoading.value = false
if (document.exists()) {
val checkIn = document.getString("checkIn")
val checkOut = document.getString("checkOut")
if (checkIn != null && checkOut == "NA") {
val checkOutData = hashMapOf(
"checkOut" to formatTimestamp(Timestamp.now()), "Status" to true
) as Map<String, Any>
attendanceRef.update(checkOutData).addOnSuccessListener {
viewModelScope.launch {
checkInCheckOutDataStore.saveLoginData(
checkOut = formatTimestamp(
Timestamp.now()
)
)
checkInCheckOutDataStore.saveIsLogIn(false)
}
Log.d("Firestore", "Check-out successful")
}.addOnFailureListener { e ->
postErrorMessage("Error adding check-out: ${e.message}")
}
} else if (checkOut != "NA") {
postErrorMessage("Already checked out for today.")
viewModelScope.launch {
_isCheckedOut.value = true
}
} else {
postErrorMessage("Cannot check out without checking in.")
}
} else {
postErrorMessage("No attendance found for today. Please check in first.")
}
}.addOnFailureListener { exception ->
_isLoading.value = false
postErrorMessage("Error fetching document: ${exception.message}")
}
}
}
}
}

View File

@@ -0,0 +1,54 @@
package `in`.sminnovations.attendanceapp.screens.login.data
// File: LoginDataStore.kt
import android.content.Context
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
private const val DATASTORE_NAME = "user_prefs"
private val Context.dataStore by preferencesDataStore(name = DATASTORE_NAME)
@Singleton
class LoginDataStore @Inject constructor(@ApplicationContext context: Context) {
private val dataStore = context.dataStore
private val LOGIN_ID_KEY = stringPreferencesKey("login_id")
private val PASSWORD_KEY = stringPreferencesKey("password")
private val IS_LOGGED_IN_KEY = booleanPreferencesKey("is_logged_in")
val loginId: Flow<String> = dataStore.data.map { preferences ->
preferences[LOGIN_ID_KEY] ?: ""
}
val password: Flow<String> = dataStore.data.map { preferences ->
preferences[PASSWORD_KEY] ?: ""
}
val isLoggedIn: Flow<Boolean> = dataStore.data.map { preferences ->
preferences[IS_LOGGED_IN_KEY] ?: false
}
suspend fun saveLoginData(id: String, password: String) {
dataStore.edit { preferences ->
preferences[LOGIN_ID_KEY] = id
preferences[PASSWORD_KEY] = password
preferences[IS_LOGGED_IN_KEY] = true
}
}
suspend fun clearLoginData() {
dataStore.edit { preferences ->
preferences[LOGIN_ID_KEY] = ""
preferences[PASSWORD_KEY] = ""
preferences[IS_LOGGED_IN_KEY] = false
}
}
}

View File

@@ -0,0 +1,6 @@
package `in`.sminnovations.attendanceapp.screens.login.data
data class LoginData(
val id : String ="",
val password: String = ""
)

View File

@@ -0,0 +1,55 @@
package `in`.sminnovations.attendanceapp.screens.login.ui.componets
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun OutlinedTextFieldWithError(
value: String,
onValueChange: (String) -> Unit,
label: String,
isError: Boolean,
errorMessage: String,
modifier: Modifier = Modifier,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
visualTransformation: VisualTransformation = VisualTransformation.None
) {
Column(modifier = modifier) {
OutlinedTextField(
value = value,
onValueChange = onValueChange,
label = { Text(label) },
isError = isError,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
visualTransformation = visualTransformation,
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary,
unfocusedBorderColor = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
focusedLabelColor = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary,
unfocusedLabelColor = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant
)
)
if (isError) {
Text(
text = errorMessage,
color = MaterialTheme.colorScheme.error,
style = TextStyle(fontSize = 12.sp),
modifier = Modifier.padding(start = 16.dp, top = 4.dp)
)
}
}
}

View File

@@ -0,0 +1,155 @@
package `in`.sminnovations.attendanceapp.screens.login.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import `in`.sminnovations.attendanceapp.R
import `in`.sminnovations.attendanceapp.screens.login.ui.componets.OutlinedTextFieldWithError
import `in`.sminnovations.attendanceapp.screens.login.viewModel.LoginViewModel
import kotlinx.coroutines.launch
@Composable
fun LoginScreen(
loginViewModel: LoginViewModel = hiltViewModel(),
onLoginSuccess: () -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val isPasswordError by loginViewModel.isPasswordError.collectAsState()
val isUserIdError by loginViewModel.isIdError.collectAsState()
val isLoggedIn by loginViewModel.isLoggedIn.collectAsState()
var userId by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
LaunchedEffect(isLoggedIn) {
if (isLoggedIn) onLoginSuccess()
}
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(horizontal = 16.dp, vertical = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Attendance App", style = MaterialTheme.typography.headlineLarge)
Spacer(modifier = Modifier.height(40.dp))
Image(
painter = painterResource(id = R.drawable.smi_logo),
contentDescription = "App Logo",
modifier = Modifier
.fillMaxWidth(0.5f)
.aspectRatio(1f)
.padding(8.dp)
)
Spacer(modifier = Modifier.height(40.dp))
Text(text = "Credentials", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.weight(1f))
OutlinedTextFieldWithError(
value = userId,
onValueChange = { userId = it },
label = "User ID",
isError = isUserIdError,
errorMessage = "Please enter a valid User ID",
modifier = Modifier.fillMaxWidth(),
leadingIcon = {
Icon(
imageVector = Icons.Default.Person,
contentDescription = "User Icon"
)
}
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextFieldWithError(
value = password,
onValueChange = { password = it },
label = "Password",
isError = isPasswordError,
errorMessage = "Please enter a valid password",
modifier = Modifier.fillMaxWidth(),
leadingIcon = {
Icon(
imageVector = Icons.Default.Lock,
contentDescription = "Password Icon"
)
},
trailingIcon = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(
painter = painterResource(
id = if (passwordVisible) R.drawable.baseline_visibility_24 else R.drawable.baseline_visibility_off_24
),
contentDescription = if (passwordVisible) "Hide Password" else "Show Password"
)
}
},
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = {
coroutineScope.launch {
loginViewModel.login(userId, password, onLoginSuccess)
}
},
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
) {
Text(
text = "Login",
fontSize = 20.sp
)
}
Spacer(modifier = Modifier.weight(1f))
}
}

View File

@@ -0,0 +1,126 @@
package `in`.sminnovations.attendanceapp.screens.login.viewModel
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FirebaseFirestore
import dagger.hilt.android.lifecycle.HiltViewModel
import `in`.sminnovations.attendanceapp.data.dataStore.Employee
import `in`.sminnovations.attendanceapp.data.dataStore.EmployeeRepository
import `in`.sminnovations.attendanceapp.screens.login.data.LoginData
import `in`.sminnovations.attendanceapp.screens.login.data.LoginDataStore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(
private val loginDataStore: LoginDataStore,
private val firebaseFirestore: FirebaseFirestore,
private val employeeRepository: EmployeeRepository
) : ViewModel() {
private val _isPasswordError = MutableStateFlow(false)
val isPasswordError = _isPasswordError.asStateFlow()
private val _isIDError = MutableStateFlow(false)
val isIdError = _isIDError.asStateFlow()
private val _isLoggedIn = MutableStateFlow(false)
val isLoggedIn = _isLoggedIn.asStateFlow()
private val _loginData = MutableStateFlow(LoginData("", ""))
val loginData = _loginData.asStateFlow()
init {
viewModelScope.launch {
collectLoginData()
}
}
fun collectLoginData(){
viewModelScope.launch {
loginDataStore.isLoggedIn.collect { loggedIn ->
_isLoggedIn.value = loggedIn
if (loggedIn) {
loginDataStore.loginId.collect { id ->
loginDataStore.password.collect { password ->
_loginData.value = LoginData(id, password)
}
}
}
}
}
}
fun login(userId: String, password: String, onLoginSuccess: () -> Unit) {
viewModelScope.launch {
firebaseFirestore.collection("employees").whereEqualTo("Id", userId).get()
.addOnSuccessListener {
if (!it.isEmpty) {
_isIDError.value = false
for (doc in it) {
if (password == doc.getString("Password")) {
if (true == doc.getBoolean("EmployeeStatus")) {
viewModelScope.launch {
loginDataStore.saveLoginData(userId, password)
_isLoggedIn.value = true
}
viewModelScope.launch {
fetchEmployeeData(userId)
onLoginSuccess()
}
}else{
_isIDError.value = true
}
} else {
_isPasswordError.value = true
}
}
} else {
_isIDError.value = true
}
}.addOnFailureListener {
_isIDError.value = true
}
}
}
private fun DocumentSnapshot.toEmployee(): Employee {
return Employee(
androidID = getString("AndroidID") ?: "",
dateOfJoining = getString("DateOfJoining") ?: "",
dateOfLeaving = getString("DateOfLeaving") ?: "",
designation = getString("Designation") ?: "",
employeeStatus = getBoolean("EmployeeStatus") ?: true,
id = getString("Id") ?: "",
name = getString("Name") ?: "",
password = getString("Password") ?: "",
photo = getString("Photo") ?: "",
dateOfBirth = getString("dateOfBirth") ?: ""
)
}
private suspend fun fetchEmployeeData(employeeId: String) {
val db = firebaseFirestore
val document = db.collection("employees").document(employeeId).get().await()
if (document.exists()) {
val savedata = document.toEmployee()
Log.d("FetchSuccess", "Fetched Employee Data: $savedata")
employeeRepository.saveEmployeeData(savedata)
} else {
Log.d("FetchError", "Document does not exist for employee ID: $employeeId")
}
}
}

View File

@@ -0,0 +1,2 @@
package `in`.sminnovations.attendanceapp.screens.qrcode.ui

View File

@@ -0,0 +1,7 @@
package `in`.sminnovations.attendanceapp.screens.settings.data
class EmployeeDataSettings (
val name : String = "",
val id : String = "",
val designation : String = ""
)

View File

@@ -0,0 +1,78 @@
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

@@ -0,0 +1,52 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui
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.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.OutlinedCard
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import `in`.sminnovations.attendanceapp.screens.settings.data.SettingsData
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.TopComponents
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.subSetting.AboutSettings
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.subSetting.Appearance
@Composable
fun SettingsScreen(
navController: NavController
) {
val context = LocalContext.current
val dataStore = SettingsData(context)
val scrollState = rememberScrollState()
val devMode = dataStore.getDevModeToggle.collectAsState(initial = false)
Column(
modifier = Modifier
.verticalScroll(scrollState)
.fillMaxSize(),
) {
Spacer(modifier = Modifier.height(20.dp))
TopComponents(navController = navController)
Spacer(modifier = Modifier.height(20.dp))
OutlinedCard(
modifier = Modifier
.padding(10.dp)
.wrapContentSize()
) {
Appearance(context = context)
AboutSettings(context = context)
}
}
}

View File

@@ -0,0 +1,88 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui.components
import androidx.compose.foundation.Image
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.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
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.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import `in`.sminnovations.attendanceapp.R
import `in`.sminnovations.attendanceapp.screens.settings.data.EmployeeDataSettings
import `in`.sminnovations.attendanceapp.screens.settings.viewModel.SettingsViewModel
@Composable
fun TopComponents(
navController: NavController,
settingsViewModel: SettingsViewModel = hiltViewModel()
) {
val employeeDataSettings by settingsViewModel.employeeDataSettings.collectAsState(
initial = EmployeeDataSettings()
)
Column(
modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally
) {
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))
Text(
text = employeeDataSettings.name,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "ID: ${employeeDataSettings.id}",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 16.sp
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = employeeDataSettings.designation,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary,
fontSize = 16.sp
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = {
settingsViewModel.logout()
navController.navigate("login")
}) {
Text(text = "Log out")
}
}
}

View File

@@ -0,0 +1,125 @@
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,
)
}
}
}
@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

@@ -0,0 +1,126 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
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.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import `in`.sminnovations.attendanceapp.R
@Composable
fun RadioDialogue(
showDialog:Boolean,
listOptions:List<String>,
headText:String,
selectedOption:String,
onClose: () -> Unit,
gotOption:(String)->Unit
) {
if (showDialog) {
Dialog(
onDismissRequest = { onClose() },
DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true,
usePlatformDefaultWidth = true
)
) {
OutlinedCard(
modifier = Modifier
.wrapContentSize()
.clickable { onClose() },
) {
BackHandler(onBack = onClose)
Box(
// modifier = Modifier
// .background(MaterialTheme.colorScheme.su)
) {
Column {
Text(
text = headText,
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(20.dp)
)
LazyColumn(
modifier =
if (listOptions.size <=5) {
Modifier
.padding(20.dp, 20.dp, 20.dp, 0.dp)
.wrapContentSize()
}else {
Modifier
.padding(20.dp, 20.dp, 20.dp, 0.dp)
.height(400.dp)
},
)
{
items(listOptions) { option ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
gotOption(option)
onClose()
},
verticalAlignment = Alignment.CenterVertically
) {
RadioButtonComponent(value = selectedOption, onValueChange = {
gotOption(option)
onClose()
}, placeholder = option)
}
Spacer(modifier = Modifier.height(10.dp))
}
}
Row {
Spacer(Modifier.weight(1f))
Text(
text = stringResource(R.string.cancel),
modifier = Modifier
.padding(0.dp, 0.dp, 40.dp, 20.dp)
.clickable { onClose() },
color = MaterialTheme.colorScheme.primary
)
}
}
}
}
}
}
}
@Composable
fun RadioButtonComponent(value: String, onValueChange: (String) -> Unit, placeholder: String) {
RadioButton(
selected = value == placeholder,
onClick = { onValueChange(placeholder) }
)
Text(
text = placeholder,
modifier = Modifier.padding(start = 8.dp)
)
}

View File

@@ -0,0 +1,40 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents
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.graphics.vector.ImageVector
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.ClickableCard
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.RadioDialogue
@Composable
fun RadioCard(
title:String,
subtitle:String,
icon: ImageVector,
radioHeader:String,
gotOption:(String)->Unit,
listOptions:List<String>,
selectedOption:String
) {
var showDialog by remember { mutableStateOf(false) }
ClickableCard(
title = title,
subtitle = subtitle,
onClick = {
showDialog = true
},
icon = icon
)
RadioDialogue(
showDialog = showDialog,
listOptions =listOptions,
headText =radioHeader ,
selectedOption = selectedOption,
onClose = { showDialog=false},
gotOption = gotOption
)
}

View File

@@ -0,0 +1,71 @@
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

@@ -0,0 +1,79 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui.components.subSetting
import android.annotation.SuppressLint
import android.content.Context
import android.widget.Toast
import androidx.compose.foundation.layout.Column
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.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import `in`.sminnovations.attendanceapp.R
import `in`.sminnovations.attendanceapp.screens.settings.data.SettingsData
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.ClickableCard
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.CombinedClickableCard
import kotlinx.coroutines.launch
@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun AboutSettings(context: Context) {
val scope = rememberCoroutineScope()
val dataStore = SettingsData(context)
var clickCount by remember { mutableIntStateOf(0) }
if (clickCount > 5) {
scope.launch {
dataStore.setDevMode(true)
}
clickCount = 0
Toast.makeText(context, "Developer mode is On : Scroll Down", Toast.LENGTH_SHORT).show()
}
Column(modifier = Modifier.padding(10.dp, 0.dp, 10.dp, 10.dp)) {
Text(
text = "About",
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier
.align(Alignment.Start)
.padding(14.dp),
color = MaterialTheme.colorScheme.primary
)
OutlinedCard {
ClickableCard(
title = "Version",
subtitle = context.packageManager.getPackageInfo(
context.packageName,
0
).versionName,
onClick = {
clickCount++
},
icon = ImageVector.vectorResource(id = R.drawable.round_info_24)
)
CombinedClickableCard(title = "Developer",
subtitle = "SM Innovations Pvt Ltd",
onClick = {},
onDoubleClick = {
},
icon = ImageVector.vectorResource(id = R.drawable.round_logo_dev_24)
)
}
}
}

View File

@@ -0,0 +1,90 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui.components.subSetting
import android.content.Context
import androidx.compose.foundation.layout.Column
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.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import `in`.sminnovations.attendanceapp.R
import `in`.sminnovations.attendanceapp.screens.settings.data.SettingsData
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.RadioCard
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.SwitchCard
import kotlinx.coroutines.launch
@Composable
fun Appearance (context: Context) {
val scope = rememberCoroutineScope()
val dataStore = SettingsData(context)
val dynamicColorsThemeValue = dataStore.getDynamicColorThemeValue.collectAsState(true)
val blackThemeValue = dataStore.getBlackThemeValue.collectAsState(initial = false)
val theme = dataStore.getThemeMode.collectAsState(initial = "System default")
Column(modifier = Modifier.padding(10.dp, 0.dp, 10.dp, 0.dp)) {
Text(
text = stringResource(R.string.appearance),
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier
.align(Alignment.Start)
.padding(14.dp),
color = MaterialTheme.colorScheme.primary
)
OutlinedCard{
RadioCard(
title = stringResource(R.string.theme),
subtitle =theme.value ,
icon = ImageVector.vectorResource(id = R.drawable.round_color_lens_24),
radioHeader = stringResource(R.string.select_theme_mode) ,
gotOption = {
scope.launch {
dataStore.saveThemeMode(it)
}
},
listOptions = listOf("Light", "Dark", "System default"),
selectedOption =theme.value
)
SwitchCard(
checkedValue = dynamicColorsThemeValue.value!!,
title = stringResource(R.string.dynamic_theme),
subtitle = stringResource(R.string.theme_based_on_wallpaper_color),
onSwitchChange =
{
scope.launch {
dataStore.setDynamicColorThemeValue(it)
}
},
icon = ImageVector.vectorResource(id = R.drawable.round_auto_mode_24)
)
SwitchCard(
checkedValue = blackThemeValue.value!!,
title = stringResource(R.string.black_theme),
subtitle = stringResource(R.string.black_theme_description),
onSwitchChange =
{
scope.launch {
dataStore.setBlackThemeValue(it)
}
},
icon = ImageVector.vectorResource(id = R.drawable.round_contrast_24)
)
}
}
}

View File

@@ -0,0 +1,77 @@
package `in`.sminnovations.attendanceapp.screens.settings.ui.components.subSetting
import android.content.Context
import android.os.Build
import android.widget.Toast
import androidx.compose.foundation.layout.Column
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.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import `in`.sminnovations.attendanceapp.R
import `in`.sminnovations.attendanceapp.screens.settings.data.SettingsData
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.ClickableCard
import `in`.sminnovations.attendanceapp.screens.settings.ui.components.settingsComponents.SwitchCard
import kotlinx.coroutines.launch
@Composable
fun Developer_settings(context: Context) {
val scope = rememberCoroutineScope()
val dataStore = SettingsData(context)
val devMode = dataStore.getDevModeToggle.collectAsState(initial = false)
Column(modifier = Modifier.padding(10.dp,0.dp,10.dp,10.dp)) {
Text(
text = stringResource(R.string.developer_options),
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier
.align(Alignment.Start)
.padding(14.dp),
color = MaterialTheme.colorScheme.primary
)
OutlinedCard {
SwitchCard(
checkedValue = devMode.value!!,
title = "Developer mode",
subtitle = "To toggle developer mode",
onSwitchChange = {
scope.launch {
dataStore.setDevMode(it)
}
},
icon = ImageVector.vectorResource(R.drawable.round_developer_mode_24)
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ClickableCard(
title = "Clear the Database",
subtitle = "you will loose every thing 😨",
onClick = {
},
icon = ImageVector.vectorResource(R.drawable.round_delete_sweep_24)
)
}
ClickableCard(
title = "Change the language",
subtitle = "Current language :English",
onClick = {
Toast.makeText(context, "Not implemented yet", Toast.LENGTH_SHORT).show()
},
icon = ImageVector.vectorResource(R.drawable.round_translate_24)
)
}
}
}

View File

@@ -0,0 +1,33 @@
package `in`.sminnovations.attendanceapp.screens.settings.viewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import `in`.sminnovations.attendanceapp.data.dataStore.EmployeeRepository
import `in`.sminnovations.attendanceapp.screens.home.data.CheckInCheckOutDataStore
import `in`.sminnovations.attendanceapp.screens.login.data.LoginDataStore
import `in`.sminnovations.attendanceapp.screens.settings.data.EmployeeDataSettings
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(
private val loginDataStore: LoginDataStore,
private val employeeRepository: EmployeeRepository,
private val checkInCheckOutDataStore: CheckInCheckOutDataStore,
) : ViewModel() {
val employeeDataSettings : Flow<EmployeeDataSettings> = employeeRepository.getEmployeeDataSettings()
fun logout() {
viewModelScope.launch {
loginDataStore.clearLoginData()
checkInCheckOutDataStore.clearLoginData()
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
<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

@@ -0,0 +1,5 @@
<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="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<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,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<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,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
</vector>

View File

@@ -0,0 +1,15 @@
<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="M18.06,2.83c-1.15,-0.77 -2.46,-1.32 -3.86,-1.61C13.58,1.1 13,1.57 13,2.21v0c0,0.46 0.31,0.88 0.76,0.97c1.17,0.23 2.26,0.7 3.21,1.34c0.39,0.26 0.9,0.19 1.23,-0.14l0,0C18.66,3.93 18.59,3.18 18.06,2.83z"/>
<path android:fillColor="@android:color/white" android:pathData="M11,2.21L11,2.21c0,-0.64 -0.58,-1.11 -1.2,-0.99c-1.4,0.29 -2.71,0.84 -3.86,1.61c-0.52,0.35 -0.59,1.1 -0.15,1.54l0,0c0.33,0.33 0.84,0.4 1.23,0.14c0.96,-0.64 2.04,-1.1 3.21,-1.34C10.69,3.09 11,2.67 11,2.21z"/>
<path android:fillColor="@android:color/white" android:pathData="M4.38,5.79L4.38,5.79C3.93,5.34 3.18,5.42 2.84,5.94C2.07,7.09 1.51,8.39 1.23,9.8C1.1,10.42 1.58,11 2.21,11h0c0.46,0 0.88,-0.31 0.97,-0.76c0.23,-1.17 0.7,-2.26 1.34,-3.22C4.77,6.64 4.7,6.12 4.38,5.79z"/>
<path android:fillColor="@android:color/white" android:pathData="M21.79,11L21.79,11c0.63,0 1.11,-0.58 0.98,-1.2c-0.29,-1.4 -0.84,-2.7 -1.61,-3.86c-0.35,-0.52 -1.1,-0.6 -1.54,-0.15l0,0c-0.33,0.33 -0.4,0.84 -0.14,1.23c0.64,0.96 1.1,2.05 1.34,3.22C20.91,10.69 21.33,11 21.79,11z"/>
<path android:fillColor="@android:color/white" android:pathData="M8,12.46l2.44,1.11L11.54,16c0.18,0.39 0.73,0.39 0.91,0l1.11,-2.44L16,12.46c0.39,-0.18 0.39,-0.73 0,-0.91l-2.44,-1.11L12.46,8c-0.18,-0.39 -0.73,-0.39 -0.91,0l-1.11,2.44L8,11.54C7.61,11.72 7.61,12.28 8,12.46z"/>
<path android:fillColor="@android:color/white" android:pathData="M12,21c-3.11,0 -5.85,-1.59 -7.46,-4H6c0.55,0 1,-0.45 1,-1v0c0,-0.55 -0.45,-1 -1,-1H2c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h0c0.55,0 1,-0.45 1,-1v-1.7c1.99,2.84 5.27,4.7 9,4.7c4.45,0 8.27,-2.64 10,-6.43c0.26,-0.57 -0.08,-1.25 -0.69,-1.39l0,0c-0.45,-0.1 -0.93,0.11 -1.12,0.54C18.77,18.83 15.64,21 12,21z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<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,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<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,22c5.52,0 10,-4.48 10,-10S17.52,2 12,2S2,6.48 2,12S6.48,22 12,22zM13,4.07c3.94,0.49 7,3.85 7,7.93s-3.05,7.44 -7,7.93V4.07z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<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

@@ -0,0 +1,5 @@
<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,5h10v1c0,0.55 0.45,1 1,1s1,-0.45 1,-1L19,3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v3c0,0.55 0.45,1 1,1s1,-0.45 1,-1L7,5zM16.12,15.88l3.17,-3.17c0.39,-0.39 0.39,-1.02 0,-1.41l-3.17,-3.17c-0.39,-0.39 -1.03,-0.39 -1.42,0 -0.39,0.39 -0.39,1.02 0,1.41L17.17,12l-2.47,2.47c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.03,0.39 1.42,0zM9.29,14.46L6.83,12l2.46,-2.46c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.03,-0.39 -1.42,0L4.7,11.3c-0.39,0.39 -0.39,1.02 0,1.41l3.17,3.17c0.39,0.39 1.03,0.39 1.42,0 0.4,-0.39 0.39,-1.03 0,-1.42zM17,19L7,19v-1c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-3c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v1z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<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>

View File

@@ -0,0 +1,7 @@
<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,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM7.68,14.98H6V9h1.71c1.28,0 1.71,1.03 1.71,1.71l0,2.56C9.42,13.95 9,14.98 7.68,14.98zM12.38,11.46v1.07h-1.18v1.39h1.93v1.07h-2.25c-0.4,0.01 -0.74,-0.31 -0.75,-0.71V9.75c-0.01,-0.4 0.31,-0.74 0.71,-0.75h2.28l0,1.07h-1.92v1.39H12.38zM16.88,14.23c-0.48,1.11 -1.33,0.89 -1.71,0L13.77,9h1.18l1.07,4.11L17.09,9h1.18L16.88,14.23z"/>
<path android:fillColor="@android:color/white" android:pathData="M7.77,10.12H7.14v3.77h0.63c0.14,0 0.28,-0.05 0.42,-0.16c0.14,-0.1 0.21,-0.26 0.21,-0.47v-2.52c0,-0.21 -0.07,-0.37 -0.21,-0.47C8.05,10.17 7.91,10.12 7.77,10.12z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<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.65,15.67c0.14,-0.36 0.05,-0.77 -0.23,-1.05l-2.09,-2.06 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53h1.94c0.54,0 0.99,-0.45 0.99,-0.99v-0.02c0,-0.54 -0.45,-0.99 -0.99,-0.99L10,4L10,3c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v1L1.99,4c-0.54,0 -0.99,0.45 -0.99,0.99 0,0.55 0.45,0.99 0.99,0.99h10.18C11.5,7.92 10.44,9.75 9,11.35c-0.81,-0.89 -1.49,-1.86 -2.06,-2.88 -0.16,-0.29 -0.45,-0.47 -0.78,-0.47 -0.69,0 -1.13,0.75 -0.79,1.35 0.63,1.13 1.4,2.21 2.3,3.21L3.3,16.87c-0.4,0.39 -0.4,1.03 0,1.42 0.39,0.39 1.02,0.39 1.42,0L9,14l2.02,2.02c0.51,0.51 1.38,0.32 1.63,-0.35zM17.5,10c-0.6,0 -1.14,0.37 -1.35,0.94l-3.67,9.8c-0.24,0.61 0.22,1.26 0.87,1.26 0.39,0 0.74,-0.24 0.88,-0.61l0.89,-2.39h4.75l0.9,2.39c0.14,0.36 0.49,0.61 0.88,0.61 0.65,0 1.11,-0.65 0.88,-1.26l-3.67,-9.8c-0.22,-0.57 -0.76,-0.94 -1.36,-0.94zM15.88,17l1.62,-4.33L19.12,17h-3.24z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,3 +1,14 @@
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<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>

View File

@@ -2,4 +2,6 @@
plugins {
alias(libs.plugins.android.application) 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

@@ -8,6 +8,8 @@ espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.6"
activityCompose = "1.9.2"
composeBom = "2023.08.00"
datastoreCoreAndroid = "1.1.1"
datastorePreferencesCoreJvm = "1.1.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -24,6 +26,8 @@ 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-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
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]
android-application = { id = "com.android.application", version.ref = "agp" }