Compare commits
76 Commits
main
...
dev-user-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1b31bc645 | ||
|
|
0447cc5b60 | ||
|
|
f578e537e0 | ||
|
|
3f0f10e2c6 | ||
|
|
527de78877 | ||
|
|
18bcfb7549 | ||
|
|
18cd04ed92 | ||
|
|
5d8fa4cb54 | ||
|
|
96048ee619 | ||
|
|
05495f2e60 | ||
|
|
d41f15925f | ||
|
|
54c309d4f6 | ||
|
|
fb3029e138 | ||
|
|
a5a314f39f | ||
|
|
8187c0b2dc | ||
|
|
d87fa7de80 | ||
|
|
41fca7375e | ||
|
|
6777faefef | ||
|
|
7c23d1b493 | ||
|
|
dfea713a26 | ||
|
|
4c2e12f075 | ||
|
|
39f43596ae | ||
|
|
b23eb10f04 | ||
|
|
17eb17a9d9 | ||
|
|
f27cddc464 | ||
|
|
4d3440fbac | ||
|
|
81597efd5c | ||
|
|
7bcc899eb9 | ||
|
|
d0ca0f88b8 | ||
|
|
a4482ee342 | ||
|
|
498bb1a38e | ||
|
|
a46ba87d44 | ||
|
|
b24e22b6d7 | ||
|
|
0a44e7eb92 | ||
|
|
61fc5132b2 | ||
|
|
032f007be6 | ||
|
|
d926526440 | ||
|
|
737de0785c | ||
|
|
8add42f1de | ||
|
|
f8e67f1a7e | ||
|
|
234b595e1f | ||
|
|
a9e548db83 | ||
|
|
e82aa38917 | ||
|
|
ef2ecefea9 | ||
|
|
14aefcf3ae | ||
|
|
9f4c4c7057 | ||
|
|
bbbbb460ad | ||
|
|
0595e791a1 | ||
|
|
81e4351695 | ||
|
|
394074e0ce | ||
|
|
3971b58472 | ||
|
|
7b3e4465bd | ||
|
|
c2de7bdd11 | ||
|
|
42ac1fc8c4 | ||
|
|
dcee750197 | ||
|
|
1ecaeb5fc1 | ||
|
|
ed354c7799 | ||
|
|
6ccab6e184 | ||
|
|
5246c8be89 | ||
|
|
43f75b454f | ||
|
|
1fcab8ae14 | ||
|
|
fa54dcba1c | ||
|
|
6018fd73d3 | ||
|
|
a7aff3c680 | ||
|
|
2372dd9589 | ||
|
|
adf1a80ae8 | ||
|
|
679fa0ef69 | ||
|
|
b6d1133da2 | ||
|
|
ad3d6f974e | ||
|
|
29edb0f115 | ||
|
|
d16a6b9c44 | ||
|
|
251f91128a | ||
|
|
cf64155a41 | ||
|
|
8524a1b56e | ||
|
|
9cafedc9a0 | ||
|
|
0a889bdbea |
101
.gitlab-ci.yml
Normal file
101
.gitlab-ci.yml
Normal 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
|
||||
14
.idea/deploymentTargetSelector.xml
generated
14
.idea/deploymentTargetSelector.xml
generated
@@ -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>
|
||||
|
||||
329
.idea/other.xml
generated
Normal file
329
.idea/other.xml
generated
Normal file
@@ -0,0 +1,329 @@
|
||||
<?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>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal 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>
|
||||
@@ -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
48
app/google-services.json
Normal 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"
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
13
app/src/main/java/in/sminnovations/attendanceapp/AppClass.kt
Normal file
13
app/src/main/java/in/sminnovations/attendanceapp/AppClass.kt
Normal 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(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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 = ""
|
||||
)
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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] ?: ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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?
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
package `in`.sminnovations.attendanceapp.screens.home.ui.componets
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package `in`.sminnovations.attendanceapp.screens.login.data
|
||||
|
||||
data class LoginData(
|
||||
val id : String ="",
|
||||
val password: String = ""
|
||||
)
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
package `in`.sminnovations.attendanceapp.screens.qrcode.ui
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package `in`.sminnovations.attendanceapp.screens.settings.data
|
||||
|
||||
class EmployeeDataSettings (
|
||||
val name : String = "",
|
||||
val id : String = "",
|
||||
val designation : String = ""
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
|
||||
5
app/src/main/res/drawable/baseline_close_24.xml
Normal file
5
app/src/main/res/drawable/baseline_close_24.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/baseline_menu_24.xml
Normal file
5
app/src/main/res/drawable/baseline_menu_24.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/baseline_visibility_24.xml
Normal file
5
app/src/main/res/drawable/baseline_visibility_24.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/baseline_visibility_off_24.xml
Normal file
5
app/src/main/res/drawable/baseline_visibility_off_24.xml
Normal 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>
|
||||
15
app/src/main/res/drawable/round_auto_mode_24.xml
Normal file
15
app/src/main/res/drawable/round_auto_mode_24.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/round_color_lens_24.xml
Normal file
5
app/src/main/res/drawable/round_color_lens_24.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/round_contrast_24.xml
Normal file
5
app/src/main/res/drawable/round_contrast_24.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/round_delete_sweep_24.xml
Normal file
5
app/src/main/res/drawable/round_delete_sweep_24.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/round_developer_mode_24.xml
Normal file
5
app/src/main/res/drawable/round_developer_mode_24.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/round_info_24.xml
Normal file
5
app/src/main/res/drawable/round_info_24.xml
Normal 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>
|
||||
7
app/src/main/res/drawable/round_logo_dev_24.xml
Normal file
7
app/src/main/res/drawable/round_logo_dev_24.xml
Normal 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>
|
||||
5
app/src/main/res/drawable/round_translate_24.xml
Normal file
5
app/src/main/res/drawable/round_translate_24.xml
Normal 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>
|
||||
BIN
app/src/main/res/drawable/smi_logo.png
Normal file
BIN
app/src/main/res/drawable/smi_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user