mirror of
https://github.com/hmalik144/Farmr.git
synced 2026-03-18 07:25:55 +00:00
Merge pull request #19 from hmalik144/modern_architecture
Modern architecture MVVM clean architecture test suite expansion CI/CD fastlane implementation
This commit is contained in:
@@ -1,26 +1,130 @@
|
|||||||
# Use the latest 2.1 version of CircleCI pipeline process engine.
|
# Use the latest 2.1 version of CircleCI pipeline process engine.
|
||||||
# See: https://circleci.com/docs/configuration-reference
|
# See: https://circleci.com/docs/2.0/configuration-reference
|
||||||
|
# For a detailed guide to building and testing on Android, read the docs:
|
||||||
|
# https://circleci.com/docs/2.0/language-android/ for more details.
|
||||||
version: 2.1
|
version: 2.1
|
||||||
|
|
||||||
# Define a job to be invoked later in a workflow.
|
# Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects.
|
||||||
# See: https://circleci.com/docs/configuration-reference/#jobs
|
# See: https://circleci.com/docs/2.0/orb-intro/
|
||||||
jobs:
|
orbs:
|
||||||
say-hello:
|
android: circleci/android@2.3.0
|
||||||
# Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub.
|
|
||||||
# See: https://circleci.com/docs/configuration-reference/#executor-job
|
commands:
|
||||||
docker:
|
setup_repo:
|
||||||
- image: cimg/base:stable
|
description: checkout repo and android dependencies
|
||||||
# Add steps to the job
|
|
||||||
# See: https://circleci.com/docs/configuration-reference/#steps
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
name: "Say hello"
|
name: Give gradle permissions
|
||||||
command: "echo Hello, World!"
|
command: |
|
||||||
|
sudo chmod +x ./gradlew
|
||||||
# Orchestrate jobs using workflows
|
- android/restore-gradle-cache
|
||||||
# See: https://circleci.com/docs/configuration-reference/#workflows
|
run_tests:
|
||||||
|
description: run tests for flavour specified
|
||||||
|
steps:
|
||||||
|
# The next step will run the unit tests
|
||||||
|
- run:
|
||||||
|
name: Run local unit tests
|
||||||
|
command: |
|
||||||
|
./gradlew testDebugUnitTest
|
||||||
|
- android/save-gradle-cache
|
||||||
|
- store_artifacts:
|
||||||
|
path: app/build/reports
|
||||||
|
destination: reports
|
||||||
|
- store_test_results:
|
||||||
|
path: app/build/test-results
|
||||||
|
run_ui_tests:
|
||||||
|
description: run instrumentation and espresso tests
|
||||||
|
steps:
|
||||||
|
- android/start-emulator-and-run-tests:
|
||||||
|
post-emulator-launch-assemble-command: ./gradlew assembleAndroidTest
|
||||||
|
test-command: ./gradlew connectedDebugAndroidTest --continue
|
||||||
|
system-image: system-images;android-26;google_apis;x86
|
||||||
|
# store screenshots for failed ui tests
|
||||||
|
- when:
|
||||||
|
condition: on_fail
|
||||||
|
steps:
|
||||||
|
- store_artifacts:
|
||||||
|
path: app/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected
|
||||||
|
destination: connected_android_test
|
||||||
|
# store test reports
|
||||||
|
- store_artifacts:
|
||||||
|
path: app/build/reports/androidTests/connected
|
||||||
|
destination: reports
|
||||||
|
- store_test_results:
|
||||||
|
path: app/build/outputs/androidTest-results/connected
|
||||||
|
deploy_to_play_store:
|
||||||
|
description: deploy to playstore
|
||||||
|
steps:
|
||||||
|
# The next step will run the unit tests
|
||||||
|
- android/decode-keystore:
|
||||||
|
keystore-location: "./app/keystore.jks"
|
||||||
|
- run:
|
||||||
|
name: Setup playstore key
|
||||||
|
command: |
|
||||||
|
echo "$GOOGLE_PLAY_KEY" > "google-play-key.json"
|
||||||
|
- run:
|
||||||
|
name: Run fastlane command to deploy to playstore
|
||||||
|
command: |
|
||||||
|
pwd
|
||||||
|
bundle exec fastlane deploy
|
||||||
|
- store_test_results:
|
||||||
|
path: fastlane/report.xml
|
||||||
|
# Define a job to be invoked later in a workflow.
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
|
||||||
|
jobs:
|
||||||
|
# Below is the definition of your job to build and test your app, you can rename and customize it as you want.
|
||||||
|
build-and-test:
|
||||||
|
# These next lines define the Android machine image executor.
|
||||||
|
# See: https://circleci.com/docs/2.0/executor-types/
|
||||||
|
executor:
|
||||||
|
name: android/android-machine
|
||||||
|
tag: 2023.05.1
|
||||||
|
# Add steps to the job
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
|
||||||
|
steps:
|
||||||
|
- setup_repo
|
||||||
|
- run_tests
|
||||||
|
run_instrumentation_test:
|
||||||
|
# These next lines define the Android machine image executor.
|
||||||
|
# See: https://circleci.com/docs/2.0/executor-types/
|
||||||
|
executor:
|
||||||
|
name: android/android-machine
|
||||||
|
tag: 2023.05.1
|
||||||
|
# Add steps to the job
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
|
||||||
|
steps:
|
||||||
|
- setup_repo
|
||||||
|
- run_ui_tests
|
||||||
|
deploy-to-playstore:
|
||||||
|
docker:
|
||||||
|
- image: cimg/android:2023.07-browsers
|
||||||
|
auth:
|
||||||
|
username: ${DOCKER_USERNAME}
|
||||||
|
password: ${DOCKER_PASSWORD}
|
||||||
|
steps:
|
||||||
|
- setup_repo
|
||||||
|
- deploy_to_play_store
|
||||||
|
# Invoke jobs via workflows
|
||||||
|
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
|
||||||
workflows:
|
workflows:
|
||||||
say-hello-workflow:
|
version: 2
|
||||||
|
build-release:
|
||||||
jobs:
|
jobs:
|
||||||
- say-hello
|
- build-and-test:
|
||||||
|
context: appttude
|
||||||
|
- run_instrumentation_test:
|
||||||
|
context: appttude
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- release
|
||||||
|
- deploy-to-playstore:
|
||||||
|
context: appttude
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- release
|
||||||
|
requires:
|
||||||
|
- build-and-test
|
||||||
102
.gitignore
vendored
102
.gitignore
vendored
@@ -1,9 +1,97 @@
|
|||||||
*.iml
|
### AndroidStudio ###
|
||||||
|
# Covers files to be ignored for android development using Android Studio.
|
||||||
|
|
||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
.gradle/
|
||||||
/.idea/workspace.xml
|
build/
|
||||||
/.idea/libraries
|
|
||||||
.DS_Store
|
# Signing files
|
||||||
/build
|
.signing/
|
||||||
/captures
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio
|
||||||
|
/*/build/
|
||||||
|
/*/local.properties
|
||||||
|
/*/out
|
||||||
|
/*/*/build
|
||||||
|
/*/*/production
|
||||||
|
captures/
|
||||||
|
.navigation/
|
||||||
|
*.ipr
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
|
||||||
|
# Google Services (e.g. APIs or Firebase)
|
||||||
|
# google-services.json
|
||||||
|
|
||||||
|
# Android Patch
|
||||||
|
gen-external-apklibs
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
|
||||||
|
# IntelliJ IDEA
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# User-specific configurations
|
||||||
|
.idea/androidTestResultsUserPreferences.xml
|
||||||
|
.idea/caches/
|
||||||
|
.idea/libraries/
|
||||||
|
.idea/shelf/
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/.name
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/copyright/profiles_settings.xml
|
||||||
|
.idea/encodings.xml
|
||||||
|
.idea/misc.xml
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/scopes/scope_settings.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/vcs.xml
|
||||||
|
.idea/jsLibraryMappings.xml
|
||||||
|
.idea/datasources.xml
|
||||||
|
.idea/dataSources.ids
|
||||||
|
.idea/sqlDataSources.xml
|
||||||
|
.idea/dynamic.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
|
||||||
|
# Gem/fastlane
|
||||||
|
Gemfile.lock
|
||||||
|
/fastlane/report.xml
|
||||||
|
# Google play files
|
||||||
|
/google-play-key.json
|
||||||
|
|||||||
22
.idea/androidTestResultsUserPreferences.xml
generated
22
.idea/androidTestResultsUserPreferences.xml
generated
@@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="AndroidTestResultsUserPreferences">
|
|
||||||
<option name="androidTestResultsTableState">
|
|
||||||
<map>
|
|
||||||
<entry key="1283002349">
|
|
||||||
<value>
|
|
||||||
<AndroidTestResultsTableState>
|
|
||||||
<option name="preferredColumnWidths">
|
|
||||||
<map>
|
|
||||||
<entry key="Duration" value="90" />
|
|
||||||
<entry key="Pixel_6_Pro_API_31" value="120" />
|
|
||||||
<entry key="Tests" value="360" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</AndroidTestResultsTableState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
127
.idea/assetWizardSettings.xml
generated
127
.idea/assetWizardSettings.xml
generated
@@ -1,127 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="WizardSettings">
|
|
||||||
<option name="children">
|
|
||||||
<map>
|
|
||||||
<entry key="imageWizard">
|
|
||||||
<value>
|
|
||||||
<PersistentState>
|
|
||||||
<option name="children">
|
|
||||||
<map>
|
|
||||||
<entry key="imageAssetPanel">
|
|
||||||
<value>
|
|
||||||
<PersistentState>
|
|
||||||
<option name="children">
|
|
||||||
<map>
|
|
||||||
<entry key="actionbar">
|
|
||||||
<value>
|
|
||||||
<PersistentState>
|
|
||||||
<option name="children">
|
|
||||||
<map>
|
|
||||||
<entry key="clipArt">
|
|
||||||
<value>
|
|
||||||
<PersistentState>
|
|
||||||
<option name="values">
|
|
||||||
<map>
|
|
||||||
<entry key="color" value="000000" />
|
|
||||||
<entry key="opacityPercent" value="80" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</PersistentState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
<option name="values">
|
|
||||||
<map>
|
|
||||||
<entry key="theme" value="HOLO_DARK" />
|
|
||||||
<entry key="themeColor" value="ffffff" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</PersistentState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
<entry key="launcher">
|
|
||||||
<value>
|
|
||||||
<PersistentState>
|
|
||||||
<option name="children">
|
|
||||||
<map>
|
|
||||||
<entry key="foregroundImage">
|
|
||||||
<value>
|
|
||||||
<PersistentState>
|
|
||||||
<option name="values">
|
|
||||||
<map>
|
|
||||||
<entry key="color" value="000000" />
|
|
||||||
<entry key="scalingPercent" value="69" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</PersistentState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
<option name="values">
|
|
||||||
<map>
|
|
||||||
<entry key="foregroundImage" value="C:\Users\h_mal\Desktop\Farmr\farmicon.png" />
|
|
||||||
<entry key="outputName" value="ic_launcher_release" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</PersistentState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
<entry key="launcherLegacy">
|
|
||||||
<value>
|
|
||||||
<PersistentState>
|
|
||||||
<option name="children">
|
|
||||||
<map>
|
|
||||||
<entry key="clipArt">
|
|
||||||
<value>
|
|
||||||
<PersistentState>
|
|
||||||
<option name="values">
|
|
||||||
<map>
|
|
||||||
<entry key="color" value="000000" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</PersistentState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</PersistentState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
<entry key="notification">
|
|
||||||
<value>
|
|
||||||
<PersistentState>
|
|
||||||
<option name="children">
|
|
||||||
<map>
|
|
||||||
<entry key="clipArt">
|
|
||||||
<value>
|
|
||||||
<PersistentState>
|
|
||||||
<option name="values">
|
|
||||||
<map>
|
|
||||||
<entry key="color" value="000000" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</PersistentState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</PersistentState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</PersistentState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</PersistentState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
BIN
.idea/caches/build_file_checksums.ser
generated
BIN
.idea/caches/build_file_checksums.ser
generated
Binary file not shown.
BIN
.idea/caches/gradle_models.ser
generated
BIN
.idea/caches/gradle_models.ser
generated
Binary file not shown.
113
.idea/codeStyles/Project.xml
generated
113
.idea/codeStyles/Project.xml
generated
@@ -1,113 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<code_scheme name="Project" version="173">
|
|
||||||
<codeStyleSettings language="XML">
|
|
||||||
<arrangement>
|
|
||||||
<rules>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>xmlns:android</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>xmlns:.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*:id</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*:name</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>name</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>style</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<rule>
|
|
||||||
<match>
|
|
||||||
<AND>
|
|
||||||
<NAME>.*</NAME>
|
|
||||||
<XML_ATTRIBUTE />
|
|
||||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
|
||||||
</AND>
|
|
||||||
</match>
|
|
||||||
<order>BY_NAME</order>
|
|
||||||
</rule>
|
|
||||||
</section>
|
|
||||||
</rules>
|
|
||||||
</arrangement>
|
|
||||||
</codeStyleSettings>
|
|
||||||
</code_scheme>
|
|
||||||
</component>
|
|
||||||
6
.idea/compiler.xml
generated
6
.idea/compiler.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<bytecodeTargetLevel target="17" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
21
.idea/gradle.xml
generated
21
.idea/gradle.xml
generated
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
|
||||||
<component name="GradleSettings">
|
|
||||||
<option name="linkedExternalProjectsSettings">
|
|
||||||
<GradleProjectSettings>
|
|
||||||
<option name="delegatedBuild" value="false" />
|
|
||||||
<option name="testRunner" value="GRADLE" />
|
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
|
||||||
<option name="gradleJvm" value="jbr-17" />
|
|
||||||
<option name="modules">
|
|
||||||
<set>
|
|
||||||
<option value="$PROJECT_DIR$" />
|
|
||||||
<option value="$PROJECT_DIR$/app" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</GradleProjectSettings>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
30
.idea/jarRepositories.xml
generated
30
.idea/jarRepositories.xml
generated
@@ -1,30 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RemoteRepositoriesConfiguration">
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="central" />
|
|
||||||
<option name="name" value="Maven Central repository" />
|
|
||||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="jboss.community" />
|
|
||||||
<option name="name" value="JBoss Community repository" />
|
|
||||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="BintrayJCenter" />
|
|
||||||
<option name="name" value="BintrayJCenter" />
|
|
||||||
<option name="url" value="https://jcenter.bintray.com/" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="maven" />
|
|
||||||
<option name="name" value="maven" />
|
|
||||||
<option name="url" value="https://jitpack.io" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="Google" />
|
|
||||||
<option name="name" value="Google" />
|
|
||||||
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
|
||||||
</remote-repository>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="1.9.0" />
|
<option name="version" value="1.7.10" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
47
.idea/misc.xml
generated
47
.idea/misc.xml
generated
@@ -1,47 +0,0 @@
|
|||||||
<project version="4">
|
|
||||||
<component name="NullableNotNullManager">
|
|
||||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
|
||||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
|
||||||
<option name="myNullables">
|
|
||||||
<value>
|
|
||||||
<list size="12">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
|
||||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
|
||||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
|
||||||
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
|
||||||
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
|
||||||
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
|
||||||
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
|
||||||
<item index="7" class="java.lang.String" itemvalue="android.annotation.Nullable" />
|
|
||||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
|
||||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
|
||||||
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
|
||||||
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
<option name="myNotNulls">
|
|
||||||
<value>
|
|
||||||
<list size="11">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
|
||||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
|
||||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
|
||||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
|
||||||
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
|
||||||
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
|
||||||
<item index="6" class="java.lang.String" itemvalue="android.annotation.NonNull" />
|
|
||||||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
|
||||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
|
||||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
|
||||||
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectType">
|
|
||||||
<option name="id" value="Android" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
12
.idea/modules.xml
generated
12
.idea/modules.xml
generated
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Farmr.iml" filepath="$PROJECT_DIR$/.idea/modules/Farmr.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Farmr.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Farmr.app.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Farmr.app.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Farmr.app.androidTest.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Farmr.app.main.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Farmr.app.main.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/Farmr.app.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/Farmr.app.unitTest.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,23 +1,40 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'org.jetbrains.kotlin.android'
|
apply plugin: 'org.jetbrains.kotlin.android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
|
def relStorePassword = System.getenv("RELEASE_STORE_PASSWORD")
|
||||||
|
def relKeyPassword = System.getenv("RELEASE_KEY_PASSWORD")
|
||||||
|
def relKeyAlias = System.getenv("RELEASE_KEY_ALIAS")
|
||||||
|
|
||||||
|
def keystorePath = "/keystore.jks"
|
||||||
|
def keystore = file(keystorePath).exists() ? file(keystorePath) : null
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 31
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.appttude.h_mal.farmr"
|
applicationId "com.appttude.h_mal.farmr"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 31
|
||||||
versionCode 1
|
versionCode 2
|
||||||
versionName "1.0"
|
versionName "2.0"
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'com.appttude.h_mal.farmr.application.TestRunner'
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
storePassword relStorePassword
|
||||||
|
keyPassword relKeyPassword
|
||||||
|
keyAlias relKeyAlias
|
||||||
|
storeFile keystore
|
||||||
|
}
|
||||||
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
useLibrary 'android.test.mock'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -29,8 +46,46 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||||
implementation 'com.google.android.material:material:1.0.0'
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation 'androidx.fragment:fragment-ktx:1.4.0'
|
||||||
|
implementation 'androidx.activity:activity-ktx:1.4.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
|
||||||
|
implementation 'androidx.preference:preference:1.2.1'
|
||||||
implementation 'com.ajts.androidmads.SQLite2Excel:library:1.0.2'
|
implementation 'com.ajts.androidmads.SQLite2Excel:library:1.0.2'
|
||||||
testImplementation 'junit:junit:4.12'
|
/ * Unit testing * /
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
||||||
|
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
|
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
|
||||||
androidTestImplementation 'androidx.test:rules:1.4.0'
|
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||||
|
/ * mockito and livedata testing * /
|
||||||
|
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||||
|
testImplementation 'androidx.arch.core:core-testing:2.1.0'
|
||||||
|
/ * MockK * /
|
||||||
|
def mockk_ver = "1.10.5"
|
||||||
|
testImplementation "io.mockk:mockk:$mockk_ver"
|
||||||
|
androidTestImplementation "io.mockk:mockk-android:$mockk_ver"
|
||||||
|
/ * Android Espresso * /
|
||||||
|
def testJunitVersion = "1.1.5"
|
||||||
|
def testRunnerVersion = "1.5.2"
|
||||||
|
def espressoVersion = "3.5.1"
|
||||||
|
androidTestImplementation "androidx.test.ext:junit:$testJunitVersion"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
|
||||||
|
androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espressoVersion"
|
||||||
|
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
|
||||||
|
androidTestImplementation "androidx.test:runner:$testRunnerVersion"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
|
||||||
|
androidTestImplementation "org.hamcrest:hamcrest:2.2"
|
||||||
|
/ * Room database * /
|
||||||
|
def room_version = "2.4.3"
|
||||||
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
|
implementation "androidx.room:room-ktx:$room_version"
|
||||||
|
/ *Kodein Dependency Injection * /
|
||||||
|
def kodein_version = "6.2.1"
|
||||||
|
implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version"
|
||||||
|
implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version"
|
||||||
|
/ * jxl * /
|
||||||
|
implementation 'net.sourceforge.jexcelapi:jxl:2.6.12'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.appttude.h_mal.farmr.application
|
||||||
|
|
||||||
|
import androidx.test.espresso.IdlingRegistry
|
||||||
|
import androidx.test.espresso.idling.CountingIdlingResource
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import com.appttude.h_mal.farmr.base.BaseApplication
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.LegacyDatabase
|
||||||
|
import com.appttude.h_mal.farmr.data.prefs.PreferenceProvider
|
||||||
|
import com.appttude.h_mal.farmr.model.Shift
|
||||||
|
|
||||||
|
class TestAppClass : BaseApplication() {
|
||||||
|
private val idlingResources = CountingIdlingResource("Data_loader")
|
||||||
|
|
||||||
|
lateinit var database: LegacyDatabase
|
||||||
|
lateinit var preferenceProvider: PreferenceProvider
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
IdlingRegistry.getInstance().register(idlingResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createDatabase(): LegacyDatabase {
|
||||||
|
database =
|
||||||
|
LegacyDatabase(InstrumentationRegistry.getInstrumentation().context.contentResolver)
|
||||||
|
return database
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createPrefs(): PreferenceProvider {
|
||||||
|
preferenceProvider = PreferenceProvider(this)
|
||||||
|
return preferenceProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addToDatabase(shift: Shift) = database.insertShiftDataIntoDatabase(shift)
|
||||||
|
fun addShiftsToDatabase(shifts: List<Shift>) = shifts.forEach { addToDatabase(it) }
|
||||||
|
fun clearDatabase() = database.deleteAllShiftsInDatabase()
|
||||||
|
fun cleanPrefs() = preferenceProvider.clearPrefs()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.appttude.h_mal.farmr.application
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.runner.AndroidJUnitRunner
|
||||||
|
|
||||||
|
class TestRunner : AndroidJUnitRunner() {
|
||||||
|
@Throws(
|
||||||
|
InstantiationException::class,
|
||||||
|
IllegalAccessException::class,
|
||||||
|
ClassNotFoundException::class
|
||||||
|
)
|
||||||
|
override fun newApplication(
|
||||||
|
cl: ClassLoader?,
|
||||||
|
className: String?,
|
||||||
|
context: Context?
|
||||||
|
): Application {
|
||||||
|
return super.newApplication(cl, TestAppClass::class.java.name, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,21 +3,23 @@ package com.appttude.h_mal.farmr.data
|
|||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import androidx.test.rule.provider.ProviderTestRule
|
import androidx.test.rule.provider.ProviderTestRule
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.CONTENT_AUTHORITY
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftProvider
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_BREAK
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.CONTENT_AUTHORITY
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DATE
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_BREAK
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DATE
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DURATION
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_PAYRATE
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DURATION
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_IN
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_PAYRATE
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_OUT
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_IN
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TOTALPAY
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_OUT
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TYPE
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TOTALPAY
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_UNIT
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TYPE
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry.CONTENT_URI
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_UNIT
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry._ID
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.CONTENT_URI
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry._ID
|
||||||
import junit.framework.TestCase.assertEquals
|
import junit.framework.TestCase.assertEquals
|
||||||
import junit.framework.TestCase.assertNull
|
import junit.framework.TestCase.assertNull
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@@ -30,6 +32,11 @@ class ShiftProviderTest {
|
|||||||
private val contentResolver: ContentResolver
|
private val contentResolver: ContentResolver
|
||||||
get() = providerRule.resolver
|
get() = providerRule.resolver
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
contentResolver.delete(CONTENT_URI, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun insertEntry_queryEntry_assertEntry() {
|
fun insertEntry_queryEntry_assertEntry() {
|
||||||
// Arrange
|
// Arrange
|
||||||
@@ -75,6 +82,8 @@ class ShiftProviderTest {
|
|||||||
// Assert
|
// Assert
|
||||||
val item = contentResolver.query(CONTENT_URI, projection, null, null, null)
|
val item = contentResolver.query(CONTENT_URI, projection, null, null, null)
|
||||||
item?.takeIf { it.moveToNext() }?.run {
|
item?.takeIf { it.moveToNext() }?.run {
|
||||||
|
val id = getLong(getColumnIndexOrThrow(_ID))
|
||||||
|
|
||||||
val descriptionColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_DESCRIPTION))
|
val descriptionColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_DESCRIPTION))
|
||||||
val dateColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_DATE))
|
val dateColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_DATE))
|
||||||
val timeInColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_TIME_IN))
|
val timeInColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_TIME_IN))
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package com.appttude.h_mal.farmr.data.legacydb
|
||||||
|
|
||||||
|
import androidx.test.rule.provider.ProviderTestRule
|
||||||
|
import com.appttude.h_mal.farmr.model.Shift
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class LegacyDatabaseTest {
|
||||||
|
@get:Rule
|
||||||
|
val providerRule: ProviderTestRule = ProviderTestRule
|
||||||
|
.Builder(ShiftProvider::class.java, ShiftsContract.CONTENT_AUTHORITY)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private lateinit var database: LegacyDatabase
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
database = LegacyDatabase(providerRule.resolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
database.deleteAllShiftsInDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun insertShift_readShift_successfulRead() {
|
||||||
|
// Arrange
|
||||||
|
val shift = Shift("adsfadsf", "2020-12-12", 12f, 12f)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
database.insertShiftDataIntoDatabase(shift)
|
||||||
|
val retrievedShift = database.readShiftsFromDatabase()?.first()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(retrievedShift?.description, shift.description)
|
||||||
|
assertEquals(retrievedShift?.date, shift.date)
|
||||||
|
assertEquals(retrievedShift?.units, shift.units)
|
||||||
|
assertEquals(retrievedShift?.rateOfPay, shift.rateOfPay)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun insertShift_updateShift_successfulRead() {
|
||||||
|
// Arrange
|
||||||
|
val shift = Shift("adsfadsf", "2020-12-12", 12f, 12f)
|
||||||
|
val updateShift = Shift("dasdads", "2020-11-12", 10f, 10f)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
database.insertShiftDataIntoDatabase(shift)
|
||||||
|
val id = database.readShiftsFromDatabase()?.first()!!.id
|
||||||
|
database.updateShiftDataIntoDatabase(
|
||||||
|
id = id,
|
||||||
|
typeString = updateShift.type.type,
|
||||||
|
descriptionString = updateShift.description,
|
||||||
|
dateString = updateShift.date,
|
||||||
|
timeInString = updateShift.timeIn ?: "",
|
||||||
|
timeOutString = updateShift.timeOut ?: "",
|
||||||
|
duration = updateShift.duration ?: 0f,
|
||||||
|
breaks = updateShift.breakMins ?: 0,
|
||||||
|
units = updateShift.units!!,
|
||||||
|
payRate = updateShift.rateOfPay,
|
||||||
|
totalPay = updateShift.totalPay
|
||||||
|
)
|
||||||
|
val retrievedShift = database.readSingleShiftWithId(id)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(retrievedShift?.description, updateShift.description)
|
||||||
|
assertEquals(retrievedShift?.date, updateShift.date)
|
||||||
|
assertEquals(retrievedShift?.units, updateShift.units)
|
||||||
|
assertEquals(retrievedShift?.rateOfPay, updateShift.rateOfPay)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun insertShift_deleteShift_databaseEmpty() {
|
||||||
|
// Arrange
|
||||||
|
val shift = Shift("adsfadsf", "2020-12-12", 12f, 12f)
|
||||||
|
val updateShift = Shift("dasdads", "2020-11-12", 10f, 10f)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
database.insertShiftDataIntoDatabase(shift)
|
||||||
|
database.insertShiftDataIntoDatabase(updateShift)
|
||||||
|
val id = database.readShiftsFromDatabase()?.first()!!.id
|
||||||
|
database.deleteSingleShift(id)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(database.readShiftsFromDatabase()?.size, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
107
app/src/androidTest/java/com/appttude/h_mal/farmr/ui/BaseTest.kt
Normal file
107
app/src/androidTest/java/com/appttude/h_mal/farmr/ui/BaseTest.kt
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.test.core.app.ActivityScenario
|
||||||
|
import androidx.test.espresso.Espresso
|
||||||
|
import androidx.test.espresso.UiController
|
||||||
|
import androidx.test.espresso.ViewAction
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions
|
||||||
|
import androidx.test.espresso.matcher.RootMatchers.withDecorView
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.rule.GrantPermissionRule
|
||||||
|
import com.appttude.h_mal.farmr.application.TestAppClass
|
||||||
|
import com.appttude.h_mal.farmr.ui.utils.getShifts
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.hamcrest.Matcher
|
||||||
|
import org.hamcrest.Matchers
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.kodein.di.android.kodein
|
||||||
|
|
||||||
|
@Suppress("EmptyMethod")
|
||||||
|
open class BaseTest<A : Activity>(
|
||||||
|
private val activity: Class<A>,
|
||||||
|
private val intentBundle: Bundle? = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
lateinit var scenario: ActivityScenario<A>
|
||||||
|
private lateinit var testApp: TestAppClass
|
||||||
|
private lateinit var testActivity: Activity
|
||||||
|
private lateinit var decorView: View
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
var permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
open fun setUp() {
|
||||||
|
val startIntent =
|
||||||
|
Intent(InstrumentationRegistry.getInstrumentation().targetContext, activity)
|
||||||
|
if (intentBundle != null) {
|
||||||
|
startIntent.replaceExtras(intentBundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
testApp =
|
||||||
|
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass
|
||||||
|
kodein(testApp)
|
||||||
|
runBlocking {
|
||||||
|
beforeLaunch()
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario = ActivityScenario.launch(startIntent)
|
||||||
|
scenario.onActivity {
|
||||||
|
testApp =
|
||||||
|
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestAppClass
|
||||||
|
onLaunch()
|
||||||
|
decorView = it.window.decorView
|
||||||
|
testActivity = it
|
||||||
|
}
|
||||||
|
afterLaunch()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getActivity() = testActivity
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
testFinished()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun beforeLaunch() {}
|
||||||
|
open fun onLaunch() {}
|
||||||
|
open fun afterLaunch() {}
|
||||||
|
open fun testFinished() {}
|
||||||
|
|
||||||
|
fun waitFor(delay: Long) {
|
||||||
|
Espresso.onView(ViewMatchers.isRoot()).perform(object : ViewAction {
|
||||||
|
override fun getConstraints(): Matcher<View> = ViewMatchers.isRoot()
|
||||||
|
override fun getDescription(): String = "wait for $delay milliseconds"
|
||||||
|
override fun perform(uiController: UiController, v: View?) {
|
||||||
|
uiController.loopMainThreadForAtLeast(delay)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
fun checkToastMessage(message: String) {
|
||||||
|
Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView)))
|
||||||
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
|
waitFor(3500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun navigateBack() = Espresso.pressBack()
|
||||||
|
|
||||||
|
fun addRandomShifts() {
|
||||||
|
testApp.addShiftsToDatabase(getShifts())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearDataBase() = testApp.clearDatabase()
|
||||||
|
fun clearPrefs() = testApp.cleanPrefs()
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.DatePicker
|
||||||
|
import android.widget.TimePicker
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.appcompat.widget.AppCompatButton
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
|
import androidx.test.espresso.Espresso.onData
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||||
|
import androidx.test.espresso.UiController
|
||||||
|
import androidx.test.espresso.ViewAction
|
||||||
|
import androidx.test.espresso.ViewInteraction
|
||||||
|
import androidx.test.espresso.action.ViewActions
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.action.ViewActions.swipeDown
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.contrib.PickerActions
|
||||||
|
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||||
|
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.*
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import com.appttude.h_mal.farmr.ui.utils.EspressoHelper.waitForView
|
||||||
|
import org.hamcrest.CoreMatchers.allOf
|
||||||
|
import org.hamcrest.CoreMatchers.anything
|
||||||
|
import org.hamcrest.CoreMatchers.equalTo
|
||||||
|
import org.hamcrest.Matcher
|
||||||
|
import org.hamcrest.Matchers
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
open class BaseTestRobot {
|
||||||
|
|
||||||
|
fun fillEditText(resId: Int, text: String?): ViewInteraction =
|
||||||
|
onView(withId(resId)).perform(
|
||||||
|
ViewActions.replaceText(text),
|
||||||
|
ViewActions.closeSoftKeyboard()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun clickButton(resId: Int): ViewInteraction =
|
||||||
|
onView((withId(resId))).perform(click())
|
||||||
|
|
||||||
|
// fun clickMenu(menuId: Int): ViewInteraction = onView()
|
||||||
|
|
||||||
|
fun matchView(resId: Int): ViewInteraction = onView(withId(resId))
|
||||||
|
|
||||||
|
fun matchViewWaitFor(resId: Int): ViewInteraction = waitForView(withId(resId))
|
||||||
|
|
||||||
|
fun matchDisplayed(resId: Int): ViewInteraction = matchView(resId).check(matches(isDisplayed()))
|
||||||
|
|
||||||
|
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
|
||||||
|
.check(matches(withText(text)))
|
||||||
|
|
||||||
|
fun matchText(viewId: Int, textId: Int): ViewInteraction = onView(withId(viewId))
|
||||||
|
.check(matches(withText(textId)))
|
||||||
|
|
||||||
|
fun matchText(resId: Int, text: String): ViewInteraction = matchText(matchView(resId), text)
|
||||||
|
|
||||||
|
fun clickListItem(listRes: Int, position: Int) {
|
||||||
|
onData(anything())
|
||||||
|
.inAdapterView(allOf(withId(listRes)))
|
||||||
|
.atPosition(position).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clickOnMenuItem(menuId: Int) {
|
||||||
|
openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getInstrumentation().context)
|
||||||
|
onView(withText(menuId)).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clickDialogButton(text: String) {
|
||||||
|
onView(withText(text)).inRoot(isDialog())
|
||||||
|
.check(matches(isDisplayed()))
|
||||||
|
.perform(click());
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction? {
|
||||||
|
return matchView(recyclerId)
|
||||||
|
.perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.scrollTo<VH>(
|
||||||
|
hasDescendant(withText(text))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> scrollToRecyclerItem(
|
||||||
|
recyclerId: Int,
|
||||||
|
resIdForString: Int
|
||||||
|
): ViewInteraction? {
|
||||||
|
return matchView(recyclerId)
|
||||||
|
.perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.scrollTo<VH>(
|
||||||
|
hasDescendant(withText(resIdForString))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> scrollToRecyclerItemByPosition(
|
||||||
|
recyclerId: Int,
|
||||||
|
position: Int
|
||||||
|
): ViewInteraction? {
|
||||||
|
return matchView(recyclerId)
|
||||||
|
.perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.scrollToPosition<VH>(position)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> clickViewInRecycler(recyclerId: Int, resIdForString: Int) {
|
||||||
|
matchView(recyclerId)
|
||||||
|
.perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.actionOnItem<VH>(
|
||||||
|
hasDescendant(withText(resIdForString)),
|
||||||
|
click()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> clickRecyclerAtPosition(recyclerId: Int, position: Int) {
|
||||||
|
matchView(recyclerId)
|
||||||
|
.perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.scrollToPosition<VH>(position),
|
||||||
|
RecyclerViewActions.actionOnItemAtPosition<VH>(position, click()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> clickViewInRecyclerAtPosition(recyclerId: Int, position: Int, subViewId: Int) {
|
||||||
|
matchView(recyclerId)
|
||||||
|
.perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.scrollToPosition<VH>(position),
|
||||||
|
RecyclerViewActions.actionOnItemAtPosition<VH>(position, object : ViewAction {
|
||||||
|
override fun getDescription(): String {
|
||||||
|
return "click on subview in RecyclerView at position: $position"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getConstraints(): Matcher<View> {
|
||||||
|
return Matchers.allOf(
|
||||||
|
isAssignableFrom(
|
||||||
|
RecyclerView::class.java
|
||||||
|
), isDisplayed()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun perform(uiController: UiController?, view: View?) {
|
||||||
|
view?.findViewById<View>(subViewId)?.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> clickOnRecyclerItemWithText(recyclerId: Int, text: String) {
|
||||||
|
matchView(recyclerId).perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.scrollTo<VH>(
|
||||||
|
hasDescendant(withText(text))
|
||||||
|
),
|
||||||
|
RecyclerViewActions.actionOnItem<VH>(
|
||||||
|
hasDescendant(withText(text)),
|
||||||
|
click()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun swipeDown(resId: Int): ViewInteraction =
|
||||||
|
onView(withId(resId)).perform(swipeDown())
|
||||||
|
|
||||||
|
fun getStringFromResource(@StringRes resId: Int): String =
|
||||||
|
Resources.getSystem().getString(resId)
|
||||||
|
|
||||||
|
fun pullToRefresh(resId: Int) {
|
||||||
|
onView(allOf(withId(resId), isDisplayed())).perform(swipeDown())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectDateInPicker(year: Int, month: Int, day: Int) {
|
||||||
|
onView(withClassName(equalTo(DatePicker::class.java.name))).perform(
|
||||||
|
PickerActions.setDate(
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
day
|
||||||
|
)
|
||||||
|
)
|
||||||
|
onView(
|
||||||
|
allOf(
|
||||||
|
withClassName(equalTo(AppCompatButton::class.java.name)),
|
||||||
|
withText("OK")
|
||||||
|
)
|
||||||
|
).perform(
|
||||||
|
click()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectTextInSpinner(id: Int, text: String) {
|
||||||
|
clickButton(id)
|
||||||
|
onView(withSpinnerText(text)).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectTimeInPicker(hours: Int, minutes: Int) {
|
||||||
|
onView(withClassName(equalTo(TimePicker::class.java.name))).perform(
|
||||||
|
PickerActions.setTime(
|
||||||
|
hours, minutes
|
||||||
|
)
|
||||||
|
)
|
||||||
|
onView(
|
||||||
|
allOf(
|
||||||
|
withClassName(equalTo(AppCompatButton::class.java.name)),
|
||||||
|
withText("OK")
|
||||||
|
)
|
||||||
|
).perform(
|
||||||
|
click()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui.robots
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import com.appttude.h_mal.farmr.ui.BaseTestRobot
|
||||||
|
|
||||||
|
fun addScreen(func: AddItemScreenRobot.() -> Unit) = AddItemScreenRobot().apply { func() }
|
||||||
|
class AddItemScreenRobot : BaseTestRobot() {
|
||||||
|
|
||||||
|
fun clickShiftType(type: ShiftType) {
|
||||||
|
when (type) {
|
||||||
|
ShiftType.HOURLY -> clickButton(R.id.hourly)
|
||||||
|
ShiftType.PIECE -> clickButton(R.id.piecerate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDescription(text: String?) = fillEditText(R.id.locationEditText, text)
|
||||||
|
fun setDate(year: Int, month: Int, day: Int) {
|
||||||
|
clickButton(R.id.dateEditText)
|
||||||
|
selectDateInPicker(year, month, day)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTimeIn(hour: Int, minutes: Int) {
|
||||||
|
clickButton(R.id.timeInEditText)
|
||||||
|
selectTimeInPicker(hour, minutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTimeOut(hour: Int, minutes: Int) {
|
||||||
|
clickButton(R.id.timeOutEditText)
|
||||||
|
selectTimeInPicker(hour, minutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setBreakTime(mins: Int) = fillEditText(R.id.breakEditText, mins.toString())
|
||||||
|
fun setUnits(units: Float) = fillEditText(R.id.unitET, units.toString())
|
||||||
|
fun setRateOfPay(rateOfPay: Float) = fillEditText(R.id.payrateET, rateOfPay.toString())
|
||||||
|
fun submit() = clickButton(R.id.submit)
|
||||||
|
|
||||||
|
fun assertTotalPay(pay: String) = matchText(R.id.totalpayval, pay)
|
||||||
|
fun assertDuration(duration: String) = matchText(R.id.ShiftDuration, duration)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui.robots
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import com.appttude.h_mal.farmr.ui.BaseTestRobot
|
||||||
|
|
||||||
|
fun filterScreen(func: FilterScreenRobot.() -> Unit) = FilterScreenRobot().apply { func() }
|
||||||
|
class FilterScreenRobot : BaseTestRobot() {
|
||||||
|
|
||||||
|
fun setDescription(text: String?) = fillEditText(R.id.filterLocationEditText, text)
|
||||||
|
|
||||||
|
fun setDateIn(year: Int, month: Int, day: Int) {
|
||||||
|
clickButton(R.id.fromdateInEditText)
|
||||||
|
selectDateInPicker(year, month, day)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDateOut(year: Int, month: Int, day: Int) {
|
||||||
|
clickButton(R.id.filterDateOutEditText)
|
||||||
|
selectDateInPicker(year, month, day)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setType(type: ShiftType?) = when(type) {
|
||||||
|
ShiftType.HOURLY -> selectTextInSpinner(R.id.TypeFilterEditText, type.type)
|
||||||
|
ShiftType.PIECE -> selectTextInSpinner(R.id.TypeFilterEditText, type.type)
|
||||||
|
null -> selectTextInSpinner(R.id.TypeFilterEditText, "")
|
||||||
|
}
|
||||||
|
fun submit() = clickButton(R.id.submitFiltered)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui.robots
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter.CurrentViewHolder
|
||||||
|
import com.appttude.h_mal.farmr.model.Order
|
||||||
|
import com.appttude.h_mal.farmr.model.Sortable
|
||||||
|
import com.appttude.h_mal.farmr.ui.BaseTestRobot
|
||||||
|
|
||||||
|
fun homeScreen(func: HomeScreenRobot.() -> Unit) = HomeScreenRobot().apply { func() }
|
||||||
|
class HomeScreenRobot : BaseTestRobot() {
|
||||||
|
|
||||||
|
fun clickOnItemWithText(text: String) = clickOnRecyclerItemWithText<CurrentViewHolder>(R.id.list_item_view, text)
|
||||||
|
fun clickOnItemAtPosition(position: Int) = clickRecyclerAtPosition<CurrentViewHolder>(R.id.list_item_view, position)
|
||||||
|
fun clickOnEdit(position: Int) = clickViewInRecyclerAtPosition<CurrentViewHolder>(R.id.list_item_view, position, R.id.imageView)
|
||||||
|
fun clickFab() = clickButton(R.id.fab1)
|
||||||
|
fun clickOnInfoIcon() = clickButton(R.id.action_favorite)
|
||||||
|
fun clickFilterInMenu() = clickOnMenuItem(R.string.filter)
|
||||||
|
fun clickClearFilterInMenu() = clickOnMenuItem(R.string.clear)
|
||||||
|
fun clickSortInMenu() = clickOnMenuItem(R.string.sort)
|
||||||
|
|
||||||
|
fun applySort(sortable: Sortable, order: Order = Order.ASCENDING) {
|
||||||
|
clickSortInMenu()
|
||||||
|
val label = sortable.label
|
||||||
|
clickDialogButton(label)
|
||||||
|
val orderLabel = order.label
|
||||||
|
clickDialogButton(orderLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui.robots
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import com.appttude.h_mal.farmr.ui.BaseTestRobot
|
||||||
|
|
||||||
|
fun viewScreen(func: ViewItemScreenRobot.() -> Unit) = ViewItemScreenRobot().apply { func() }
|
||||||
|
class ViewItemScreenRobot : BaseTestRobot() {
|
||||||
|
|
||||||
|
fun matchShiftType(type: ShiftType) {
|
||||||
|
when (type) {
|
||||||
|
ShiftType.HOURLY -> matchText(R.id.details_shift, type.type)
|
||||||
|
ShiftType.PIECE -> matchText(R.id.details_shift, type.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun matchDescription(text: String) = matchText(R.id.details_desc, text)
|
||||||
|
fun matchDate(date: String) {
|
||||||
|
matchText(R.id.details_date, date)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun matchTime(timeIn: String, timeOut: String) {
|
||||||
|
matchText(R.id.details_time, "$timeIn-$timeOut")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun matchBreakTime(mins: Int) = matchText(R.id.details_breaks, mins.toString())
|
||||||
|
fun matchUnits(units: Float) = fillEditText(R.id.details_units, units.toString())
|
||||||
|
fun matchRateOfPay(rateOfPay: Float) = fillEditText(R.id.details_pay_rate, rateOfPay.toString())
|
||||||
|
fun matchTotalPay(pay: String) = matchText(R.id.details_totalpay, pay)
|
||||||
|
fun matchDuration(duration: String) = matchText(R.id.details_duration, duration)
|
||||||
|
|
||||||
|
fun clickEdit() = clickButton(R.id.details_edit)
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui.tests
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.model.Order
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import com.appttude.h_mal.farmr.model.Sortable
|
||||||
|
import com.appttude.h_mal.farmr.ui.BaseTest
|
||||||
|
import com.appttude.h_mal.farmr.ui.MainActivity
|
||||||
|
import com.appttude.h_mal.farmr.ui.robots.addScreen
|
||||||
|
import com.appttude.h_mal.farmr.ui.robots.filterScreen
|
||||||
|
import com.appttude.h_mal.farmr.ui.robots.homeScreen
|
||||||
|
import com.appttude.h_mal.farmr.ui.robots.viewScreen
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ShiftTests : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
|
override fun afterLaunch() {
|
||||||
|
super.afterLaunch()
|
||||||
|
addRandomShifts()
|
||||||
|
|
||||||
|
// Content resolver hard to mock
|
||||||
|
// Dirty technique to have a populated list
|
||||||
|
homeScreen {
|
||||||
|
clickFab()
|
||||||
|
navigateBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun testFinished() {
|
||||||
|
super.testFinished()
|
||||||
|
clearDataBase()
|
||||||
|
clearPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a shift successfully
|
||||||
|
@Test
|
||||||
|
fun openAddScreen_addNewShift_newShiftCreated() {
|
||||||
|
homeScreen {
|
||||||
|
clickFab()
|
||||||
|
}
|
||||||
|
addScreen {
|
||||||
|
setDescription("This is a description")
|
||||||
|
setDate(2023, 2, 11)
|
||||||
|
clickShiftType(ShiftType.HOURLY)
|
||||||
|
setTimeIn(12, 0)
|
||||||
|
setTimeOut(14, 30)
|
||||||
|
setBreakTime(30)
|
||||||
|
setRateOfPay(10f)
|
||||||
|
assertDuration("2.0 hours")
|
||||||
|
assertTotalPay("£20.00")
|
||||||
|
submit()
|
||||||
|
}
|
||||||
|
homeScreen {
|
||||||
|
clickOnItemWithText("This is a description")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit a shift successfully
|
||||||
|
@Test
|
||||||
|
fun test2() {
|
||||||
|
homeScreen {
|
||||||
|
clickOnEdit(0)
|
||||||
|
}
|
||||||
|
addScreen {
|
||||||
|
setDescription("Edited this shift")
|
||||||
|
setTimeIn(12, 0)
|
||||||
|
setTimeOut(14, 30)
|
||||||
|
setBreakTime(30)
|
||||||
|
setRateOfPay(20f)
|
||||||
|
assertDuration("2.0 hours")
|
||||||
|
assertTotalPay("£40.00")
|
||||||
|
submit()
|
||||||
|
}
|
||||||
|
homeScreen {
|
||||||
|
clickOnItemWithText("Edited this shift")
|
||||||
|
}
|
||||||
|
viewScreen {
|
||||||
|
matchDescription("Edited this shift")
|
||||||
|
matchDuration("2 Hours 0 Minutes (+ 30 minutes break)")
|
||||||
|
matchTotalPay("2.0 Hours @ £20.00 per Hour\nEquals: £40.00")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter the list with date from
|
||||||
|
@Test
|
||||||
|
fun test3() {
|
||||||
|
homeScreen {
|
||||||
|
applySort(Sortable.TYPE, Order.DESCENDING)
|
||||||
|
clickOnItemAtPosition(0)
|
||||||
|
viewScreen {
|
||||||
|
matchDescription("Day five")
|
||||||
|
matchShiftType(ShiftType.PIECE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter the list with date to
|
||||||
|
@Test
|
||||||
|
fun test4() {
|
||||||
|
homeScreen {
|
||||||
|
clickFilterInMenu()
|
||||||
|
}
|
||||||
|
filterScreen {
|
||||||
|
setDateIn(2023,8,3)
|
||||||
|
setDateOut(2023,8,6)
|
||||||
|
submit()
|
||||||
|
}
|
||||||
|
homeScreen {
|
||||||
|
clickOnItemAtPosition(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a shift as piece rate
|
||||||
|
@Test
|
||||||
|
fun test5() {
|
||||||
|
homeScreen {
|
||||||
|
clickFab()
|
||||||
|
}
|
||||||
|
addScreen {
|
||||||
|
setDescription("This is a description")
|
||||||
|
setDate(2023, 2, 11)
|
||||||
|
clickShiftType(ShiftType.PIECE)
|
||||||
|
setRateOfPay(10f)
|
||||||
|
setUnits(1f)
|
||||||
|
assertTotalPay("£10.00")
|
||||||
|
submit()
|
||||||
|
}
|
||||||
|
homeScreen {
|
||||||
|
clickOnItemWithText("This is a description")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the details screen
|
||||||
|
@Test
|
||||||
|
fun test6() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter, sort, order and then reset
|
||||||
|
@Test
|
||||||
|
fun test7() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui.utils
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui.utils
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.model.Shift
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
|
||||||
|
fun getShifts() = listOf(
|
||||||
|
Shift(
|
||||||
|
ShiftType.HOURLY,
|
||||||
|
"Day one",
|
||||||
|
"2023-08-01",
|
||||||
|
"12:00",
|
||||||
|
"13:00",
|
||||||
|
1f,
|
||||||
|
0,
|
||||||
|
0f,
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
Shift(
|
||||||
|
ShiftType.HOURLY,
|
||||||
|
"Day two",
|
||||||
|
"2023-08-02",
|
||||||
|
"12:00",
|
||||||
|
"13:00",
|
||||||
|
1f,
|
||||||
|
0,
|
||||||
|
0f,
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
Shift(
|
||||||
|
ShiftType.HOURLY,
|
||||||
|
"Day three",
|
||||||
|
"2023-08-03",
|
||||||
|
"12:00",
|
||||||
|
"13:00",
|
||||||
|
1f,
|
||||||
|
30,
|
||||||
|
0f,
|
||||||
|
10f,
|
||||||
|
5f
|
||||||
|
),
|
||||||
|
Shift(
|
||||||
|
ShiftType.HOURLY,
|
||||||
|
"Day four",
|
||||||
|
"2023-08-04",
|
||||||
|
"12:00",
|
||||||
|
"13:00",
|
||||||
|
1f,
|
||||||
|
30,
|
||||||
|
0f,
|
||||||
|
10f,
|
||||||
|
5f
|
||||||
|
),
|
||||||
|
Shift(
|
||||||
|
ShiftType.PIECE,
|
||||||
|
"Day five",
|
||||||
|
"2023-08-05",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0f,
|
||||||
|
0,
|
||||||
|
1f,
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
Shift(
|
||||||
|
ShiftType.PIECE,
|
||||||
|
"Day six",
|
||||||
|
"2023-08-06",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0f,
|
||||||
|
0,
|
||||||
|
1f,
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
Shift(
|
||||||
|
ShiftType.PIECE,
|
||||||
|
"Day seven",
|
||||||
|
"2023-08-07",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0f,
|
||||||
|
0,
|
||||||
|
1f,
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
Shift(
|
||||||
|
ShiftType.PIECE,
|
||||||
|
"Day eight",
|
||||||
|
"2023-08-08",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0f,
|
||||||
|
0,
|
||||||
|
1f,
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui.utils
|
||||||
|
|
||||||
|
import android.os.SystemClock.sleep
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.Checkable
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.NoMatchingViewException
|
||||||
|
import androidx.test.espresso.UiController
|
||||||
|
import androidx.test.espresso.ViewAction
|
||||||
|
import androidx.test.espresso.ViewInteraction
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||||
|
import androidx.test.espresso.util.TreeIterables
|
||||||
|
import org.hamcrest.BaseMatcher
|
||||||
|
import org.hamcrest.CoreMatchers.isA
|
||||||
|
import org.hamcrest.Description
|
||||||
|
import org.hamcrest.Matcher
|
||||||
|
|
||||||
|
|
||||||
|
object EspressoHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform action of waiting for a certain view within a single root view
|
||||||
|
* @param viewMatcher Generic Matcher used to find our view
|
||||||
|
*/
|
||||||
|
fun searchFor(viewMatcher: Matcher<View>): ViewAction {
|
||||||
|
|
||||||
|
return object : ViewAction {
|
||||||
|
|
||||||
|
override fun getConstraints(): Matcher<View> = isRoot()
|
||||||
|
override fun getDescription(): String {
|
||||||
|
return "searching for view $this in the root view"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun perform(uiController: UiController, view: View) {
|
||||||
|
var tries = 0
|
||||||
|
val childViews: Iterable<View> = TreeIterables.breadthFirstViewTraversal(view)
|
||||||
|
|
||||||
|
// Look for the match in the tree of child views
|
||||||
|
childViews.forEach {
|
||||||
|
tries++
|
||||||
|
if (viewMatcher.matches(it)) {
|
||||||
|
// found the view
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw NoMatchingViewException.Builder()
|
||||||
|
.withRootView(view)
|
||||||
|
.withViewMatcher(viewMatcher)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an action to check/uncheck a checkbox
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun setChecked(checked: Boolean): ViewAction {
|
||||||
|
return object : ViewAction {
|
||||||
|
override fun getConstraints(): BaseMatcher<View> {
|
||||||
|
return object : BaseMatcher<View>() {
|
||||||
|
override fun describeTo(description: Description?) {}
|
||||||
|
|
||||||
|
override fun matches(actual: Any?): Boolean {
|
||||||
|
return isA(CheckBox::class.java).matches(actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDescription(): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun perform(uiController: UiController, view: View) {
|
||||||
|
val checkableView = view as Checkable
|
||||||
|
checkableView.isChecked = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform action of implicitly waiting for a certain view.
|
||||||
|
* This differs from EspressoExtensions.searchFor in that,
|
||||||
|
* upon failure to locate an element, it will fetch a new root view
|
||||||
|
* in which to traverse searching for our @param match
|
||||||
|
*
|
||||||
|
* @param viewMatcher ViewMatcher used to find our view
|
||||||
|
*/
|
||||||
|
fun waitForView(
|
||||||
|
viewMatcher: Matcher<View>,
|
||||||
|
waitMillis: Int = 5000,
|
||||||
|
waitMillisPerTry: Long = 100
|
||||||
|
): ViewInteraction {
|
||||||
|
|
||||||
|
// Derive the max tries
|
||||||
|
val maxTries = waitMillis / waitMillisPerTry.toInt()
|
||||||
|
|
||||||
|
var tries = 0
|
||||||
|
|
||||||
|
for (i in 0..maxTries)
|
||||||
|
try {
|
||||||
|
// Track the amount of times we've tried
|
||||||
|
tries++
|
||||||
|
|
||||||
|
// Search the root for the view
|
||||||
|
onView(isRoot()).perform(searchFor(viewMatcher))
|
||||||
|
|
||||||
|
// If we're here, we found our view. Now return it
|
||||||
|
return onView(viewMatcher)
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
|
||||||
|
if (tries == maxTries) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
sleep(waitMillisPerTry)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception("Error finding a view matching $viewMatcher")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui.utils
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
fun <T> LiveData<T>.getOrAwaitValue(
|
||||||
|
time: Long = 2,
|
||||||
|
timeUnit: TimeUnit = TimeUnit.SECONDS
|
||||||
|
): T {
|
||||||
|
var data: T? = null
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
val observer = object : Observer<T> {
|
||||||
|
override fun onChanged(o: T?) {
|
||||||
|
data = o
|
||||||
|
latch.countDown()
|
||||||
|
this@getOrAwaitValue.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.observeForever(observer)
|
||||||
|
|
||||||
|
// Don't wait indefinitely if the LiveData is not set.
|
||||||
|
if (!latch.await(time, timeUnit)) {
|
||||||
|
throw TimeoutException("LiveData value was never set.")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return data as T
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".di.ShiftApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
|
|
||||||
<!-- Splash screen -->
|
<!-- Splash screen -->
|
||||||
<activity
|
<activity
|
||||||
android:name="com.appttude.h_mal.farmr.SplashScreen"
|
android:name="com.appttude.h_mal.farmr.ui.SplashScreen"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@android:style/Theme.Black.NoTitleBar"
|
android:theme="@android:style/Theme.Black.NoTitleBar"
|
||||||
tools:ignore="LockedOrientationActivity"
|
tools:ignore="LockedOrientationActivity"
|
||||||
@@ -27,15 +28,25 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="com.appttude.h_mal.farmr.MainActivity"
|
android:name="com.appttude.h_mal.farmr.ui.MainActivity"
|
||||||
android:parentActivityName="com.appttude.h_mal.farmr.SplashScreen"
|
android:parentActivityName="com.appttude.h_mal.farmr.ui.SplashScreen"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
android:exported="true"/>
|
android:exported="true"/>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="com.appttude.h_mal.farmr.data.ShiftProvider"
|
android:name="com.appttude.h_mal.farmr.data.legacydb.ShiftProvider"
|
||||||
android:authorities="com.appttude.h_mal.farmr"
|
android:authorities="com.appttude.h_mal.farmr"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.provider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/provider_path" />
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package com.appttude.h_mal.farmr
|
|
||||||
|
|
||||||
import android.app.DatePickerDialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.TextUtils
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.Spinner
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
|
|
||||||
import java.text.MessageFormat
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class FilterDataFragment : Fragment() {
|
|
||||||
private val spinnerList: Array<String> = arrayOf("", "Hourly", "Piece Rate")
|
|
||||||
private val listArgs: MutableList<String> = ArrayList()
|
|
||||||
private var LocationET: EditText? = null
|
|
||||||
private var dateFromET: EditText? = null
|
|
||||||
private var dateToET: EditText? = null
|
|
||||||
private var typeSpinner: Spinner? = null
|
|
||||||
lateinit var mcurrentDate: Calendar
|
|
||||||
private lateinit var activity: MainActivity
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?): View {
|
|
||||||
val activity = (activity)
|
|
||||||
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
val rootView: View = inflater.inflate(R.layout.fragment_filter_data, container, false)
|
|
||||||
activity.setActionBarTitle(getString(R.string.title_activity_filter_data))
|
|
||||||
mcurrentDate = Calendar.getInstance()
|
|
||||||
LocationET = rootView.findViewById<View>(R.id.filterLocationEditText) as EditText?
|
|
||||||
dateFromET = rootView.findViewById<View>(R.id.fromdateInEditText) as EditText?
|
|
||||||
dateToET = rootView.findViewById<View>(R.id.filterDateOutEditText) as EditText?
|
|
||||||
typeSpinner = rootView.findViewById<View>(R.id.TypeFilterEditText) as Spinner?
|
|
||||||
|
|
||||||
|
|
||||||
if (activity.selection != null && activity.selection!!.contains(" AND " + ShiftsEntry.COLUMN_SHIFT_DESCRIPTION + " LIKE ?")) {
|
|
||||||
var str: String = activity.args!!.get(2)
|
|
||||||
str = str.replace("%", "")
|
|
||||||
LocationET!!.setText(str)
|
|
||||||
}
|
|
||||||
if (activity.selection != null && !(activity.args!!.get(0) == "2000-01-01")) {
|
|
||||||
dateFromET!!.setText(activity.args!!.get(0))
|
|
||||||
}
|
|
||||||
if ((activity.selection != null) && (activity.args != null) && !(activity.args!![1] == (mcurrentDate.get(Calendar.YEAR).toString() + "-"
|
|
||||||
+ String.format("%02d", (mcurrentDate.get(Calendar.MONTH) + 1)) + "-"
|
|
||||||
+ String.format("%02d", mcurrentDate.get(Calendar.DAY_OF_MONTH))).toString())) {
|
|
||||||
dateToET!!.setText(activity.args!![1])
|
|
||||||
}
|
|
||||||
dateFromET!!.setOnClickListener { //To show current date in the datepicker
|
|
||||||
var mYear: Int = mcurrentDate.get(Calendar.YEAR)
|
|
||||||
var mMonth: Int = mcurrentDate.get(Calendar.MONTH)
|
|
||||||
var mDay: Int = mcurrentDate.get(Calendar.DAY_OF_MONTH)
|
|
||||||
if (!(dateFromET!!.text.toString() == "")) {
|
|
||||||
val dateString: String = dateFromET!!.text.toString().trim()
|
|
||||||
mYear = dateString.substring(0, 4).toInt()
|
|
||||||
mMonth = dateString.substring(5, 7).toInt()
|
|
||||||
if (mMonth == 1) {
|
|
||||||
mMonth = 0
|
|
||||||
} else {
|
|
||||||
mMonth = mMonth - 1
|
|
||||||
}
|
|
||||||
mDay = dateString.substring(8).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
val mDatePicker = DatePickerDialog((context)!!, { datepicker, selectedyear, selectedmonth, selectedday ->
|
|
||||||
val input = MessageFormat.format("{0}-{1}-{2}", selectedyear, String.format("%02d", (selectedmonth + 1)), String.format("%02d", selectedday))
|
|
||||||
dateFromET!!.setText(input)
|
|
||||||
}, mYear, mMonth, mDay)
|
|
||||||
mDatePicker.setTitle("Select date")
|
|
||||||
mDatePicker.show()
|
|
||||||
}
|
|
||||||
dateToET!!.setOnClickListener { //To show current date in the datepicker
|
|
||||||
val mcurrentDate: Calendar = Calendar.getInstance()
|
|
||||||
var mYear: Int = mcurrentDate.get(Calendar.YEAR)
|
|
||||||
var mMonth: Int = mcurrentDate.get(Calendar.MONTH)
|
|
||||||
var mDay: Int = mcurrentDate.get(Calendar.DAY_OF_MONTH)
|
|
||||||
if (!(dateToET!!.text.toString() == "")) {
|
|
||||||
val dateString: String = dateToET!!.text.toString().trim({ it <= ' ' })
|
|
||||||
mYear = dateString.substring(0, 4).toInt()
|
|
||||||
mMonth = dateString.substring(5, 7).toInt()
|
|
||||||
if (mMonth == 1) {
|
|
||||||
mMonth = 0
|
|
||||||
} else {
|
|
||||||
mMonth -= 1
|
|
||||||
}
|
|
||||||
mDay = dateString.substring(8).toInt()
|
|
||||||
}
|
|
||||||
val mDatePicker = DatePickerDialog((context)!!, { datepicker, selectedyear, selectedmonth, selectedday ->
|
|
||||||
val input = MessageFormat.format("{0}-{1}-{2}", selectedyear, String.format("%02d", (selectedmonth + 1)), String.format("%02d", selectedday))
|
|
||||||
dateToET!!.setText(input)
|
|
||||||
}, mYear, mMonth, mDay)
|
|
||||||
mDatePicker.setTitle("Select date")
|
|
||||||
mDatePicker.show()
|
|
||||||
}
|
|
||||||
val adapter: ArrayAdapter<String> = ArrayAdapter((context)!!, android.R.layout.simple_spinner_dropdown_item, spinnerList)
|
|
||||||
typeSpinner!!.adapter = adapter
|
|
||||||
if (activity.selection != null && activity.selection!!.contains(" AND " + ShiftsEntry.COLUMN_SHIFT_TYPE + " IS ?")) {
|
|
||||||
val spinnerPosition: Int = adapter.getPosition(activity.args!!.get(activity.args!!.size - 1))
|
|
||||||
typeSpinner!!.setSelection(spinnerPosition)
|
|
||||||
}
|
|
||||||
val submit: Button = rootView.findViewById<View>(R.id.submitFiltered) as Button
|
|
||||||
submit.setOnClickListener {
|
|
||||||
BuildQuery()
|
|
||||||
activity.args = listArgs.toTypedArray<String>()
|
|
||||||
FragmentMain.NEW_LOADER = 1
|
|
||||||
activity.fragmentManager!!.popBackStack()
|
|
||||||
}
|
|
||||||
return rootView
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BuildQuery() {
|
|
||||||
val dateQuery: String = ShiftsEntry.COLUMN_SHIFT_DATE + " BETWEEN ? AND ?"
|
|
||||||
val descQuery: String = " AND " + ShiftsEntry.COLUMN_SHIFT_DESCRIPTION + " LIKE ?"
|
|
||||||
val typeQuery: String = " AND " + ShiftsEntry.COLUMN_SHIFT_TYPE + " IS ?"
|
|
||||||
var dateFrom = "2000-01-01"
|
|
||||||
val c: Date = Calendar.getInstance().time
|
|
||||||
val df = SimpleDateFormat("yyyy-MM-dd")
|
|
||||||
var dateTo: String = df.format(c)
|
|
||||||
if (!TextUtils.isEmpty(dateFromET!!.text.toString().trim())) {
|
|
||||||
dateFrom = dateFromET!!.text.toString().trim()
|
|
||||||
}
|
|
||||||
if (!TextUtils.isEmpty(dateToET!!.text.toString().trim())) {
|
|
||||||
dateTo = dateToET!!.text.toString().trim()
|
|
||||||
}
|
|
||||||
activity.selection = dateQuery
|
|
||||||
listArgs.add(dateFrom)
|
|
||||||
listArgs.add(dateTo)
|
|
||||||
if (!TextUtils.isEmpty(LocationET!!.text.toString().trim())) {
|
|
||||||
activity.selection = activity.selection + descQuery
|
|
||||||
listArgs.add("%" + LocationET!!.text.toString().trim() + "%")
|
|
||||||
}
|
|
||||||
if (!(typeSpinner!!.selectedItem.toString() == "")) {
|
|
||||||
activity.selection = activity.selection + typeQuery
|
|
||||||
listArgs.add(typeSpinner!!.selectedItem.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,507 +0,0 @@
|
|||||||
package com.appttude.h_mal.farmr
|
|
||||||
|
|
||||||
import android.app.DatePickerDialog
|
|
||||||
import android.app.DatePickerDialog.OnDateSetListener
|
|
||||||
import android.app.TimePickerDialog
|
|
||||||
import android.app.TimePickerDialog.OnTimeSetListener
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.database.Cursor
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextUtils
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.DatePicker
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.ProgressBar
|
|
||||||
import android.widget.RadioButton
|
|
||||||
import android.widget.RadioGroup
|
|
||||||
import android.widget.ScrollView
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.TimePicker
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.loader.app.LoaderManager
|
|
||||||
import androidx.loader.content.CursorLoader
|
|
||||||
import androidx.loader.content.Loader
|
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
|
|
||||||
import java.util.Calendar
|
|
||||||
|
|
||||||
class FragmentAddItem : Fragment(), LoaderManager.LoaderCallbacks<Cursor?> {
|
|
||||||
lateinit var activity: MainActivity
|
|
||||||
|
|
||||||
private var mCurrentProductUri: Uri? = null
|
|
||||||
private var mRadioButtonOne: RadioButton? = null
|
|
||||||
private var mRadioButtonTwo: RadioButton? = null
|
|
||||||
private var mLocationEditText: EditText? = null
|
|
||||||
private var mDateEditText: EditText? = null
|
|
||||||
private var mDurationTextView: TextView? = null
|
|
||||||
private var mTimeInEditText: EditText? = null
|
|
||||||
private var mTimeOutEditText: EditText? = null
|
|
||||||
private var mBreakEditText: EditText? = null
|
|
||||||
private var mUnitEditText: EditText? = null
|
|
||||||
private var mPayRateEditText: EditText? = null
|
|
||||||
private var mTotalPayTextView: TextView? = null
|
|
||||||
private var hourlyDataView: LinearLayout? = null
|
|
||||||
private var unitsHolder: LinearLayout? = null
|
|
||||||
private var durationHolder: LinearLayout? = null
|
|
||||||
private var wholeView: LinearLayout? = null
|
|
||||||
private var scrollView: ScrollView? = null
|
|
||||||
private var progressBarAI: ProgressBar? = null
|
|
||||||
private var mBreaks = 0
|
|
||||||
private var mDay = 0
|
|
||||||
private var mMonth = 0
|
|
||||||
private var mYear = 0
|
|
||||||
private var mHoursIn = 0
|
|
||||||
private var mMinutesIn = 0
|
|
||||||
private var mHoursOut = 0
|
|
||||||
private var mMinutesOut = 0
|
|
||||||
private var mUnits = 0f
|
|
||||||
private var mPayRate = 0f
|
|
||||||
private var mType: String? = null
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?): View? {
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
val rootView = inflater.inflate(R.layout.fragment_add_item, container, false)
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
activity = (requireActivity() as MainActivity)
|
|
||||||
|
|
||||||
progressBarAI = rootView.findViewById<View>(R.id.pd_ai) as ProgressBar
|
|
||||||
scrollView = rootView.findViewById<View>(R.id.total_view) as ScrollView
|
|
||||||
mRadioGroup = rootView.findViewById<View>(R.id.rg) as RadioGroup
|
|
||||||
mRadioButtonOne = rootView.findViewById<View>(R.id.hourly) as RadioButton
|
|
||||||
mRadioButtonTwo = rootView.findViewById<View>(R.id.piecerate) as RadioButton
|
|
||||||
mLocationEditText = rootView.findViewById<View>(R.id.locationEditText) as EditText
|
|
||||||
mDateEditText = rootView.findViewById<View>(R.id.dateEditText) as EditText
|
|
||||||
mTimeInEditText = rootView.findViewById<View>(R.id.timeInEditText) as EditText
|
|
||||||
mBreakEditText = rootView.findViewById<View>(R.id.breakEditText) as EditText
|
|
||||||
mTimeOutEditText = rootView.findViewById<View>(R.id.timeOutEditText) as EditText
|
|
||||||
mDurationTextView = rootView.findViewById<View>(R.id.ShiftDuration) as TextView
|
|
||||||
mUnitEditText = rootView.findViewById<View>(R.id.unitET) as EditText
|
|
||||||
mPayRateEditText = rootView.findViewById<View>(R.id.payrateET) as EditText
|
|
||||||
mTotalPayTextView = rootView.findViewById<View>(R.id.totalpayval) as TextView
|
|
||||||
hourlyDataView = rootView.findViewById<View>(R.id.hourly_data_holder) as LinearLayout
|
|
||||||
unitsHolder = rootView.findViewById<View>(R.id.units_holder) as LinearLayout
|
|
||||||
durationHolder = rootView.findViewById<View>(R.id.duration_holder) as LinearLayout
|
|
||||||
wholeView = rootView.findViewById<View>(R.id.whole_view) as LinearLayout
|
|
||||||
mPayRate = 0.0f
|
|
||||||
mUnits = 0.0f
|
|
||||||
val b = arguments
|
|
||||||
if (b != null) {
|
|
||||||
mCurrentProductUri = Uri.parse(b.getString("uri"))
|
|
||||||
}
|
|
||||||
if (mCurrentProductUri == null) {
|
|
||||||
activity.setActionBarTitle(getString(R.string.add_item_title))
|
|
||||||
wholeView!!.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
activity.setActionBarTitle(getString(R.string.edit_item_title))
|
|
||||||
loaderManager.initLoader(EXISTING_PRODUCT_LOADER, null, this)
|
|
||||||
}
|
|
||||||
mBreakEditText!!.hint = "insert break in minutes"
|
|
||||||
mRadioGroup!!.setOnCheckedChangeListener(RadioGroup.OnCheckedChangeListener { radioGroup, i ->
|
|
||||||
if (mRadioButtonOne!!.isChecked) {
|
|
||||||
mType = mRadioButtonOne!!.text.toString()
|
|
||||||
wholeView!!.visibility = View.VISIBLE
|
|
||||||
unitsHolder!!.visibility = View.GONE
|
|
||||||
hourlyDataView!!.visibility = View.VISIBLE
|
|
||||||
durationHolder!!.visibility = View.VISIBLE
|
|
||||||
} else if (mRadioButtonTwo!!.isChecked) {
|
|
||||||
mType = mRadioButtonTwo!!.text.toString()
|
|
||||||
wholeView!!.visibility = View.VISIBLE
|
|
||||||
unitsHolder!!.visibility = View.VISIBLE
|
|
||||||
hourlyDataView!!.visibility = View.GONE
|
|
||||||
durationHolder!!.visibility = View.GONE
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mDateEditText!!.setOnClickListener(object : View.OnClickListener {
|
|
||||||
override fun onClick(v: View) {
|
|
||||||
//To show current date in the datepicker
|
|
||||||
if (TextUtils.isEmpty(mDateEditText!!.text.toString().trim { it <= ' ' })) {
|
|
||||||
val mcurrentDate = Calendar.getInstance()
|
|
||||||
mYear = mcurrentDate[Calendar.YEAR]
|
|
||||||
mMonth = mcurrentDate[Calendar.MONTH]
|
|
||||||
mDay = mcurrentDate[Calendar.DAY_OF_MONTH]
|
|
||||||
} else {
|
|
||||||
val dateString = mDateEditText!!.text.toString().trim { it <= ' ' }
|
|
||||||
mYear = dateString.substring(0, 4).toInt()
|
|
||||||
mMonth = dateString.substring(5, 7).toInt()
|
|
||||||
if (mMonth == 1) {
|
|
||||||
mMonth = 0
|
|
||||||
} else {
|
|
||||||
mMonth = mMonth - 1
|
|
||||||
}
|
|
||||||
mDay = dateString.substring(8).toInt()
|
|
||||||
}
|
|
||||||
val mDatePicker = DatePickerDialog((context)!!, object : OnDateSetListener {
|
|
||||||
override fun onDateSet(datepicker: DatePicker, selectedyear: Int, selectedmonth: Int, selectedday: Int) {
|
|
||||||
var selectedmonth = selectedmonth
|
|
||||||
mDateEditText!!.setText(
|
|
||||||
(selectedyear.toString() + "-"
|
|
||||||
+ String.format("%02d", (selectedmonth + 1.also { selectedmonth = it })) + "-"
|
|
||||||
+ String.format("%02d", selectedday))
|
|
||||||
)
|
|
||||||
setDate(selectedyear, selectedmonth, selectedday)
|
|
||||||
}
|
|
||||||
}, mYear, mMonth, mDay)
|
|
||||||
mDatePicker.setTitle("Select date")
|
|
||||||
mDatePicker.show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mTimeInEditText!!.setOnClickListener(object : View.OnClickListener {
|
|
||||||
override fun onClick(v: View) {
|
|
||||||
if ((mTimeInEditText!!.text.toString() == "")) {
|
|
||||||
val mcurrentTime = Calendar.getInstance()
|
|
||||||
mHoursIn = mcurrentTime[Calendar.HOUR_OF_DAY]
|
|
||||||
mMinutesIn = mcurrentTime[Calendar.MINUTE]
|
|
||||||
} else {
|
|
||||||
mHoursIn = (mTimeInEditText!!.text.toString().subSequence(0, 2)).toString().toInt()
|
|
||||||
mMinutesIn = (mTimeInEditText!!.text.toString().subSequence(3, 5)).toString().toInt()
|
|
||||||
}
|
|
||||||
val mTimePicker: TimePickerDialog
|
|
||||||
mTimePicker = TimePickerDialog(context, object : OnTimeSetListener {
|
|
||||||
override fun onTimeSet(timePicker: TimePicker, selectedHour: Int, selectedMinute: Int) {
|
|
||||||
val ddTime = String.format("%02d", selectedHour) + ":" + String.format("%02d", selectedMinute)
|
|
||||||
setTime(selectedMinute, selectedHour)
|
|
||||||
mTimeInEditText!!.setText(ddTime)
|
|
||||||
}
|
|
||||||
}, mHoursIn, mMinutesIn, true) //Yes 24 hour time
|
|
||||||
mTimePicker.setTitle("Select Time")
|
|
||||||
mTimePicker.show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mTimeOutEditText!!.setOnClickListener(object : View.OnClickListener {
|
|
||||||
override fun onClick(v: View) {
|
|
||||||
if ((mTimeOutEditText!!.text.toString() == "")) {
|
|
||||||
val mcurrentTime = Calendar.getInstance()
|
|
||||||
mHoursOut = mcurrentTime[Calendar.HOUR_OF_DAY]
|
|
||||||
mMinutesOut = mcurrentTime[Calendar.MINUTE]
|
|
||||||
} else {
|
|
||||||
mHoursOut = (mTimeOutEditText!!.text.toString().subSequence(0, 2)).toString().toInt()
|
|
||||||
mMinutesOut = (mTimeOutEditText!!.text.toString().subSequence(3, 5)).toString().toInt()
|
|
||||||
}
|
|
||||||
val mTimePicker: TimePickerDialog
|
|
||||||
mTimePicker = TimePickerDialog(context, object : OnTimeSetListener {
|
|
||||||
override fun onTimeSet(timePicker: TimePicker, selectedHour: Int, selectedMinute: Int) {
|
|
||||||
val ddTime = String.format("%02d", selectedHour) + ":" + String.format("%02d", selectedMinute)
|
|
||||||
setTime2(selectedMinute, selectedHour)
|
|
||||||
mTimeOutEditText!!.setText(ddTime)
|
|
||||||
}
|
|
||||||
}, mHoursOut, mMinutesOut, true) //Yes 24 hour time
|
|
||||||
mTimePicker.setTitle("Select Time")
|
|
||||||
mTimePicker.show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mTimeInEditText!!.addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, aft: Int) {}
|
|
||||||
override fun afterTextChanged(s: Editable) {
|
|
||||||
setDuration()
|
|
||||||
calculateTotalPay()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mTimeOutEditText!!.addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, aft: Int) {}
|
|
||||||
override fun afterTextChanged(s: Editable) {
|
|
||||||
setDuration()
|
|
||||||
calculateTotalPay()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mBreakEditText!!.addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, aft: Int) {}
|
|
||||||
override fun afterTextChanged(s: Editable) {
|
|
||||||
setDuration()
|
|
||||||
calculateTotalPay()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mUnitEditText!!.addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
override fun afterTextChanged(editable: Editable) {
|
|
||||||
// if(mRadioButtonTwo.isChecked()) {
|
|
||||||
// mUnits = 0.0f;
|
|
||||||
// if (!mUnitEditText.getText().toString().equals("")){
|
|
||||||
// mUnits = Float.parseFloat(mUnitEditText.getText().toString());
|
|
||||||
// }
|
|
||||||
// mPayRate = 0.0f;
|
|
||||||
// if (!mPayRateEditText.getText().toString().equals("")){
|
|
||||||
// mPayRate = Float.parseFloat(mPayRateEditText.getText().toString());
|
|
||||||
// }
|
|
||||||
// Float total = mPayRate * mUnits;
|
|
||||||
// mTotalPayTextView.setText(String.valueOf(total));
|
|
||||||
// }
|
|
||||||
calculateTotalPay()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
mPayRateEditText!!.addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
override fun afterTextChanged(editable: Editable) {
|
|
||||||
calculateTotalPay()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
val SubmitProduct = rootView.findViewById<View>(R.id.submit) as Button
|
|
||||||
SubmitProduct.setOnClickListener(object : View.OnClickListener {
|
|
||||||
override fun onClick(view: View) {
|
|
||||||
saveProduct()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return rootView
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateTotalPay() {
|
|
||||||
var total = 0.0f
|
|
||||||
mPayRate = 0.0f
|
|
||||||
if (mRadioButtonTwo!!.isChecked) {
|
|
||||||
mUnits = 0.0f
|
|
||||||
if (mUnitEditText!!.text.toString() != "") {
|
|
||||||
mUnits = mUnitEditText!!.text.toString().toFloat()
|
|
||||||
}
|
|
||||||
if (mPayRateEditText!!.text.toString() != "") {
|
|
||||||
mPayRate = mPayRateEditText!!.text.toString().toFloat()
|
|
||||||
}
|
|
||||||
total = mPayRate * mUnits
|
|
||||||
mTotalPayTextView!!.text = total.toString()
|
|
||||||
} else if (mRadioButtonOne!!.isChecked) {
|
|
||||||
if (mPayRateEditText!!.text.toString() != "") {
|
|
||||||
mPayRate = mPayRateEditText!!.text.toString().toFloat()
|
|
||||||
total = mPayRate * calculateDuration(mHoursIn, mMinutesIn, mHoursOut, mMinutesOut, mBreaks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mTotalPayTextView!!.text = String.format("%.2f", total)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater)
|
|
||||||
menu.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveProduct() {
|
|
||||||
val typeString = mType
|
|
||||||
val descriptionString = mLocationEditText!!.text.toString().trim { it <= ' ' }
|
|
||||||
if (TextUtils.isEmpty(descriptionString)) {
|
|
||||||
Toast.makeText(context, "please insert Location", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val dateString = mDateEditText!!.text.toString().trim { it <= ' ' }
|
|
||||||
if (TextUtils.isEmpty(dateString)) {
|
|
||||||
Toast.makeText(context, "please insert Date", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var timeInString: String = ""
|
|
||||||
var timeOutString: String = ""
|
|
||||||
var breaks = 0
|
|
||||||
var units = 0f
|
|
||||||
var duration = 0f
|
|
||||||
var payRate = 0f
|
|
||||||
val payRateString = mPayRateEditText!!.text.toString().trim { it <= ' ' }
|
|
||||||
if (!TextUtils.isEmpty(payRateString)) {
|
|
||||||
payRate = payRateString.toFloat()
|
|
||||||
}
|
|
||||||
var totalPay = 0f
|
|
||||||
if ((typeString == "Hourly")) {
|
|
||||||
timeInString = mTimeInEditText!!.text.toString().trim { it <= ' ' }
|
|
||||||
if (TextUtils.isEmpty(timeInString)) {
|
|
||||||
Toast.makeText(context, "please insert Time in", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
timeOutString = mTimeOutEditText!!.text.toString().trim { it <= ' ' }
|
|
||||||
if (TextUtils.isEmpty(timeOutString)) {
|
|
||||||
Toast.makeText(context, "please insert Time out", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val breakMins = mBreakEditText!!.text.toString().trim { it <= ' ' }
|
|
||||||
if (!TextUtils.isEmpty(breakMins)) {
|
|
||||||
breaks = breakMins.toInt()
|
|
||||||
if ((breaks.toFloat() / 60) > calculateDurationWithoutBreak(mHoursIn, mMinutesIn, mHoursOut, mMinutesOut)) {
|
|
||||||
Toast.makeText(context, "Break larger than duration", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
duration = calculateDuration(mHoursIn, mMinutesIn, mHoursOut, mMinutesOut, breaks)
|
|
||||||
totalPay = duration * payRate
|
|
||||||
} else if ((typeString == "Piece Rate")) {
|
|
||||||
val unitsString = mUnitEditText!!.text.toString().trim { it <= ' ' }
|
|
||||||
if (TextUtils.isEmpty(unitsString) || unitsString.toFloat() <= 0) {
|
|
||||||
Toast.makeText(context, "Insert Units", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
units = unitsString.toFloat()
|
|
||||||
}
|
|
||||||
duration = 0f
|
|
||||||
totalPay = units * payRate
|
|
||||||
}
|
|
||||||
val values = ContentValues()
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TYPE, typeString)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION, descriptionString)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_DATE, dateString)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TIME_IN, timeInString)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TIME_OUT, timeOutString)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_DURATION, duration)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_BREAK, breaks)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_UNIT, units)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_PAYRATE, payRate)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TOTALPAY, totalPay)
|
|
||||||
if (mCurrentProductUri == null) {
|
|
||||||
val newUri = activity.contentResolver.insert(ShiftsEntry.CONTENT_URI, values)
|
|
||||||
if (newUri == null) {
|
|
||||||
Toast.makeText(context, getString(R.string.insert_item_failed),
|
|
||||||
Toast.LENGTH_SHORT).show()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, getString(R.string.insert_item_successful),
|
|
||||||
Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val rowsAffected = activity.contentResolver.update(mCurrentProductUri!!, values, null, null)
|
|
||||||
if (rowsAffected == 0) {
|
|
||||||
Toast.makeText(context, getString(R.string.update_item_failed),
|
|
||||||
Toast.LENGTH_SHORT).show()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, getString(R.string.update_item_successful),
|
|
||||||
Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(activity.fragmentManager)!!.popBackStack("main", 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setDuration() {
|
|
||||||
val mcurrentTime = Calendar.getInstance()
|
|
||||||
mBreaks = 0
|
|
||||||
if (mBreakEditText!!.text.toString() != "") {
|
|
||||||
mBreaks = mBreakEditText!!.text.toString().toInt()
|
|
||||||
}
|
|
||||||
if ((mTimeOutEditText!!.text.toString() == "")) {
|
|
||||||
mHoursOut = mcurrentTime[Calendar.HOUR_OF_DAY]
|
|
||||||
mMinutesOut = mcurrentTime[Calendar.MINUTE]
|
|
||||||
} else {
|
|
||||||
mHoursOut = (mTimeOutEditText!!.text.toString().subSequence(0, 2)).toString().toInt()
|
|
||||||
mMinutesOut = (mTimeOutEditText!!.text.toString().subSequence(3, 5)).toString().toInt()
|
|
||||||
}
|
|
||||||
if ((mTimeInEditText!!.text.toString() == "")) {
|
|
||||||
mHoursIn = mcurrentTime[Calendar.HOUR_OF_DAY]
|
|
||||||
mMinutesIn = mcurrentTime[Calendar.MINUTE]
|
|
||||||
} else {
|
|
||||||
mHoursIn = (mTimeInEditText!!.text.toString().subSequence(0, 2)).toString().toInt()
|
|
||||||
mMinutesIn = (mTimeInEditText!!.text.toString().subSequence(3, 5)).toString().toInt()
|
|
||||||
}
|
|
||||||
mDurationTextView!!.text = calculateDuration(mHoursIn, mMinutesIn, mHoursOut, mMinutesOut, mBreaks).toString() + " hours"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setDate(year: Int, month: Int, day: Int) {
|
|
||||||
mYear = year
|
|
||||||
mMonth = month
|
|
||||||
mDay = day
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setTime(minutes: Int, hours: Int) {
|
|
||||||
mMinutesIn = minutes
|
|
||||||
mHoursIn = hours
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setTime2(minutes: Int, hours: Int) {
|
|
||||||
mMinutesOut = minutes
|
|
||||||
mHoursOut = hours
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateDuration(hoursIn: Int, minutesIn: Int, hoursOut: Int, minutesOut: Int, breaks: Int): Float {
|
|
||||||
val duration: Float
|
|
||||||
if (hoursOut > hoursIn) {
|
|
||||||
duration = ((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60))) - (breaks.toFloat() / 60)
|
|
||||||
} else {
|
|
||||||
duration = (((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60))) - (breaks.toFloat() / 60) + 24)
|
|
||||||
}
|
|
||||||
val s = String.format("%.2f", duration)
|
|
||||||
return s.toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateDurationWithoutBreak(hoursIn: Int, minutesIn: Int, hoursOut: Int, minutesOut: Int): Float {
|
|
||||||
val duration: Float
|
|
||||||
if (hoursOut > hoursIn) {
|
|
||||||
duration = ((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60)))
|
|
||||||
} else {
|
|
||||||
duration = (((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60))) + 24)
|
|
||||||
}
|
|
||||||
val s = String.format("%.2f", duration)
|
|
||||||
return s.toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor?> {
|
|
||||||
progressBarAI!!.visibility = View.VISIBLE
|
|
||||||
scrollView!!.visibility = View.GONE
|
|
||||||
val projection = arrayOf<String?>(
|
|
||||||
ShiftsEntry._ID,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DATE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TIME_IN,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_BREAK,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DURATION,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TYPE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_PAYRATE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_UNIT,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
|
|
||||||
return CursorLoader((context)!!, (mCurrentProductUri)!!,
|
|
||||||
projection, null, null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoaderReset(loader: Loader<Cursor?>) {}
|
|
||||||
|
|
||||||
override fun onLoadFinished(loader: Loader<Cursor?>, cursor: Cursor?) {
|
|
||||||
progressBarAI!!.visibility = View.GONE
|
|
||||||
scrollView!!.visibility = View.VISIBLE
|
|
||||||
if (cursor == null || cursor.count < 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (cursor.moveToFirst()) {
|
|
||||||
val descriptionColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)
|
|
||||||
val dateColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DATE)
|
|
||||||
val timeInColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TIME_IN)
|
|
||||||
val timeOutColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)
|
|
||||||
val breakColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_BREAK)
|
|
||||||
val durationColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DURATION)
|
|
||||||
val typeColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TYPE)
|
|
||||||
val unitColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_UNIT)
|
|
||||||
val payrateColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_PAYRATE)
|
|
||||||
val totalPayColumnIndex = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
|
|
||||||
val type = cursor.getString(typeColumnIndex)
|
|
||||||
val description = cursor.getString(descriptionColumnIndex)
|
|
||||||
val date = cursor.getString(dateColumnIndex)
|
|
||||||
val timeIn = cursor.getString(timeInColumnIndex)
|
|
||||||
val timeOut = cursor.getString(timeOutColumnIndex)
|
|
||||||
val breaks = cursor.getInt(breakColumnIndex)
|
|
||||||
val duration = cursor.getFloat(durationColumnIndex)
|
|
||||||
val unit = cursor.getFloat(unitColumnIndex)
|
|
||||||
val payrate = cursor.getFloat(payrateColumnIndex)
|
|
||||||
val totalPay = cursor.getFloat(totalPayColumnIndex)
|
|
||||||
mLocationEditText!!.setText(description)
|
|
||||||
mDateEditText!!.setText(date)
|
|
||||||
if ((type == "Hourly") || (type == "hourly")) {
|
|
||||||
mRadioButtonOne!!.isChecked = true
|
|
||||||
mRadioButtonTwo!!.isChecked = false
|
|
||||||
mTimeInEditText!!.setText(timeIn)
|
|
||||||
mTimeOutEditText!!.setText(timeOut)
|
|
||||||
mBreakEditText!!.setText(Integer.toString(breaks))
|
|
||||||
mDurationTextView!!.text = String.format("%.2f", duration) + " Hours"
|
|
||||||
} else if ((type == "Piece Rate")) {
|
|
||||||
mRadioButtonOne!!.isChecked = false
|
|
||||||
mRadioButtonTwo!!.isChecked = true
|
|
||||||
mUnitEditText!!.setText(java.lang.Float.toString(unit))
|
|
||||||
}
|
|
||||||
mPayRateEditText!!.setText(String.format("%.2f", payrate))
|
|
||||||
mTotalPayTextView!!.text = String.format("%.2f", totalPay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val EXISTING_PRODUCT_LOADER = 0
|
|
||||||
var mRadioGroup: RadioGroup? = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,459 +0,0 @@
|
|||||||
package com.appttude.h_mal.farmr
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.database.Cursor
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Environment
|
|
||||||
import android.os.StrictMode
|
|
||||||
import android.os.StrictMode.VmPolicy
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ListView
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentTransaction
|
|
||||||
import androidx.loader.app.LoaderManager
|
|
||||||
import androidx.loader.content.CursorLoader
|
|
||||||
import androidx.loader.content.Loader
|
|
||||||
import com.ajts.androidmads.library.SQLiteToExcel
|
|
||||||
import com.ajts.androidmads.library.SQLiteToExcel.ExportListener
|
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
|
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsDbHelper
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class FragmentMain : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|
||||||
var mCursorAdapter: ShiftsCursorAdapter? = null
|
|
||||||
var shiftsDbhelper: ShiftsDbHelper? = null
|
|
||||||
lateinit var defaultLoaderCallback: LoaderManager.LoaderCallbacks<Cursor>
|
|
||||||
lateinit var activity: MainActivity
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?): View? {
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
val rootView = inflater.inflate(R.layout.fragment_main, container, false)
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
activity = (requireActivity() as MainActivity)
|
|
||||||
|
|
||||||
activity.setActionBarTitle(getString(R.string.app_name))
|
|
||||||
activity.filter = activity.getSharedPreferences("PREFS", 0)
|
|
||||||
activity.sortOrder = activity.filter?.getString("Filter", null)
|
|
||||||
defaultLoaderCallback = this
|
|
||||||
val productListView = rootView.findViewById<View>(R.id.list_item_view) as ListView
|
|
||||||
val emptyView = rootView.findViewById<View>(R.id.empty_view)
|
|
||||||
productListView.emptyView = emptyView
|
|
||||||
mCursorAdapter = ShiftsCursorAdapter(activity, null)
|
|
||||||
productListView.adapter = mCursorAdapter
|
|
||||||
loaderManager.initLoader(DEFAULT_LOADER, null, defaultLoaderCallback)
|
|
||||||
val fab = rootView.findViewById<FloatingActionButton>(R.id.fab1)
|
|
||||||
fab.setOnClickListener {
|
|
||||||
val fragmentTransaction: FragmentTransaction = activity.fragmentManager!!.beginTransaction()
|
|
||||||
fragmentTransaction.replace(R.id.container, FragmentAddItem()).addToBackStack("additem").commit()
|
|
||||||
}
|
|
||||||
return rootView
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.delete_all -> {
|
|
||||||
deleteAllProducts()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.help -> {
|
|
||||||
AlertDialog.Builder(context)
|
|
||||||
.setTitle("Help & Support:")
|
|
||||||
.setView(R.layout.dialog_layout)
|
|
||||||
.setPositiveButton(android.R.string.yes) { arg0, arg1 -> }.create().show()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.filter_data -> {
|
|
||||||
val fragmentTransaction: FragmentTransaction = activity.fragmentManager!!.beginTransaction()
|
|
||||||
fragmentTransaction.replace(R.id.container, FilterDataFragment()).addToBackStack("filterdata").commit()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.sort_data -> {
|
|
||||||
sortData()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.clear_filter -> {
|
|
||||||
activity.args = null
|
|
||||||
activity.selection = null
|
|
||||||
NEW_LOADER = 0
|
|
||||||
loaderManager.restartLoader(DEFAULT_LOADER, null, this)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.export_data -> {
|
|
||||||
if (checkStoragePermissions(activity)) {
|
|
||||||
AlertDialog.Builder(context)
|
|
||||||
.setTitle("Export?")
|
|
||||||
.setMessage("Exporting current filtered data. Continue?")
|
|
||||||
.setNegativeButton(android.R.string.no, null)
|
|
||||||
.setPositiveButton(android.R.string.yes) { arg0, arg1 -> ExportData() }.create().show()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, "Storage permissions required", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.action_favorite -> {
|
|
||||||
AlertDialog.Builder(context)
|
|
||||||
.setTitle("Info:")
|
|
||||||
.setMessage(retrieveInfo())
|
|
||||||
.setPositiveButton(android.R.string.yes) { arg0, arg1 -> }.create().show()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sortData() {
|
|
||||||
val grpname = arrayOf("Added", "Date", "Name")
|
|
||||||
val sortQuery = arrayOf<String?>("")
|
|
||||||
var checkedItem = -1
|
|
||||||
if (activity.sortOrder != null && activity.sortOrder!!.contains(ShiftsEntry._ID)) {
|
|
||||||
checkedItem = 0
|
|
||||||
sortQuery[0] = ShiftsEntry._ID
|
|
||||||
} else if (activity.sortOrder != null && activity.sortOrder!!.contains(ShiftsEntry.COLUMN_SHIFT_DATE)) {
|
|
||||||
checkedItem = 1
|
|
||||||
sortQuery[0] = ShiftsEntry.COLUMN_SHIFT_DATE
|
|
||||||
} else if (activity.sortOrder != null && activity.sortOrder!!.contains(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)) {
|
|
||||||
checkedItem = 2
|
|
||||||
sortQuery[0] = ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
|
|
||||||
}
|
|
||||||
val alt_bld = AlertDialog.Builder(context)
|
|
||||||
//alt_bld.setIcon(R.drawable.icon);
|
|
||||||
alt_bld.setTitle("Sort by:")
|
|
||||||
alt_bld.setSingleChoiceItems(grpname, checkedItem, DialogInterface.OnClickListener { dialog, item ->
|
|
||||||
when (item) {
|
|
||||||
0 -> {
|
|
||||||
sortQuery[0] = ShiftsEntry._ID
|
|
||||||
return@OnClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
1 -> {
|
|
||||||
sortQuery[0] = ShiftsEntry.COLUMN_SHIFT_DATE
|
|
||||||
return@OnClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
2 -> sortQuery[0] = ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
|
|
||||||
}
|
|
||||||
}).setPositiveButton("Ascending") { dialog, id ->
|
|
||||||
activity.sortOrder = sortQuery[0] + " ASC"
|
|
||||||
activity.filter!!.edit().putString("Filter", activity.sortOrder).apply()
|
|
||||||
loaderManager.restartLoader(DEFAULT_LOADER, null, defaultLoaderCallback)
|
|
||||||
dialog.dismiss()
|
|
||||||
}.setNegativeButton("Descending") { dialog, id ->
|
|
||||||
activity.sortOrder = sortQuery[0] + " DESC"
|
|
||||||
activity.filter!!.edit().putString("Filter", activity.sortOrder).apply()
|
|
||||||
loaderManager.restartLoader(DEFAULT_LOADER, null, defaultLoaderCallback)
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
val alert = alt_bld.create()
|
|
||||||
alert.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteAllProducts() {
|
|
||||||
AlertDialog.Builder(context)
|
|
||||||
.setTitle("Delete?")
|
|
||||||
.setMessage("Are you sure you want to delete all date?")
|
|
||||||
.setNegativeButton(android.R.string.no, null)
|
|
||||||
.setPositiveButton(android.R.string.yes) { arg0, arg1 ->
|
|
||||||
val rowsDeleted = activity.contentResolver.delete(ShiftsEntry.CONTENT_URI, null, null)
|
|
||||||
Toast.makeText(context, "$rowsDeleted Items Deleted", Toast.LENGTH_SHORT).show()
|
|
||||||
}.create().show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
if (NEW_LOADER > DEFAULT_LOADER) {
|
|
||||||
loaderManager.restartLoader(DEFAULT_LOADER, null, defaultLoaderCallback)
|
|
||||||
println("reloading loader")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ExportData() {
|
|
||||||
val permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
Toast.makeText(context, "Storage permissions not granted", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
shiftsDbhelper = ShiftsDbHelper(context)
|
|
||||||
val database = shiftsDbhelper!!.writableDatabase
|
|
||||||
val projection_export = arrayOf<String?>(
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DATE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TIME_IN,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_BREAK,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DURATION,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TYPE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_UNIT,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_PAYRATE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
|
|
||||||
val cursor = activity.contentResolver.query(
|
|
||||||
ShiftsEntry.CONTENT_URI,
|
|
||||||
projection_export,
|
|
||||||
activity.selection,
|
|
||||||
activity.args,
|
|
||||||
activity.sortOrder)
|
|
||||||
database.delete(ShiftsEntry.TABLE_NAME_EXPORT, null, null)
|
|
||||||
var totalDuration = 0.00f
|
|
||||||
var totalUnits = 0.00f
|
|
||||||
var totalPay = 0.00f
|
|
||||||
try {
|
|
||||||
while (cursor!!.moveToNext()) {
|
|
||||||
val descriptionColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION))
|
|
||||||
val dateColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DATE))
|
|
||||||
val timeInColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TIME_IN))
|
|
||||||
val timeOutColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TIME_OUT))
|
|
||||||
val durationColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DURATION))
|
|
||||||
val breakOutColumnIndex = cursor.getInt(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_BREAK))
|
|
||||||
val typeColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TYPE))
|
|
||||||
val unitColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_UNIT))
|
|
||||||
val payrateColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_PAYRATE))
|
|
||||||
val totalpayColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TOTALPAY))
|
|
||||||
totalUnits = totalUnits + unitColumnIndex
|
|
||||||
totalDuration = totalDuration + durationColumnIndex
|
|
||||||
totalPay = totalPay + totalpayColumnIndex
|
|
||||||
val values = ContentValues()
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION, descriptionColumnIndex)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_DATE, dateColumnIndex)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TIME_IN, timeInColumnIndex)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TIME_OUT, timeOutColumnIndex)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_BREAK, breakOutColumnIndex)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_DURATION, durationColumnIndex)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TYPE, typeColumnIndex)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_UNIT, unitColumnIndex)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_PAYRATE, payrateColumnIndex)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TOTALPAY, totalpayColumnIndex)
|
|
||||||
database.insert(ShiftsEntry.TABLE_NAME_EXPORT, null, values)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("FragmentMain", "ExportData: ", e)
|
|
||||||
} finally {
|
|
||||||
val values = ContentValues()
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION, "")
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_DATE, "")
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TIME_IN, "")
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TIME_OUT, "")
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_BREAK, "Total duration:")
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_DURATION, totalDuration)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TYPE, "Total units:")
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_UNIT, totalUnits)
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_PAYRATE, "Total pay:")
|
|
||||||
values.put(ShiftsEntry.COLUMN_SHIFT_TOTALPAY, totalPay)
|
|
||||||
database.insert(ShiftsEntry.TABLE_NAME_EXPORT, null, values)
|
|
||||||
cursor!!.close()
|
|
||||||
}
|
|
||||||
val savePath = Environment.getExternalStorageDirectory().toString() + "/ShifttrackerTemp"
|
|
||||||
val file = File(savePath)
|
|
||||||
if (!file.exists()) {
|
|
||||||
file.mkdirs()
|
|
||||||
}
|
|
||||||
val sqLiteToExcel = SQLiteToExcel(context, "shifts.db", savePath)
|
|
||||||
sqLiteToExcel.exportSingleTable("shiftsexport", "shifthistory.xls", object : ExportListener {
|
|
||||||
override fun onStart() {}
|
|
||||||
override fun onCompleted(filePath: String) {
|
|
||||||
Toast.makeText(context, filePath, Toast.LENGTH_SHORT).show()
|
|
||||||
val newPath = Uri.parse("file://$savePath/shifthistory.xls")
|
|
||||||
val builder = VmPolicy.Builder()
|
|
||||||
StrictMode.setVmPolicy(builder.build())
|
|
||||||
val emailintent = Intent(Intent.ACTION_SEND)
|
|
||||||
emailintent.type = "application/vnd.ms-excel"
|
|
||||||
emailintent.putExtra(Intent.EXTRA_SUBJECT, "historic shifts")
|
|
||||||
emailintent.putExtra(Intent.EXTRA_TEXT, "I'm email body.")
|
|
||||||
emailintent.putExtra(Intent.EXTRA_STREAM, newPath)
|
|
||||||
startActivity(Intent.createChooser(emailintent, "Send Email"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(e: Exception) {
|
|
||||||
println("Error msg: $e")
|
|
||||||
Toast.makeText(context, "Failed to Export data", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun retrieveInfo(): String {
|
|
||||||
val projection = arrayOf<String?>(
|
|
||||||
ShiftsEntry._ID,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DATE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TIME_IN,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_BREAK,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DURATION,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TYPE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_UNIT,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_PAYRATE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
|
|
||||||
val cursor = activity.contentResolver.query(
|
|
||||||
ShiftsEntry.CONTENT_URI,
|
|
||||||
projection,
|
|
||||||
activity.selection,
|
|
||||||
activity.args,
|
|
||||||
activity.sortOrder)
|
|
||||||
var totalDuration = 0.0f
|
|
||||||
var countOfTypeH = 0
|
|
||||||
var countOfTypeP = 0
|
|
||||||
var totalUnits = 0f
|
|
||||||
var totalPay = 0f
|
|
||||||
var lines = 0
|
|
||||||
try {
|
|
||||||
while (cursor!!.moveToNext()) {
|
|
||||||
val durationColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DURATION))
|
|
||||||
val typeColumnIndex = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TYPE))
|
|
||||||
val unitColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_UNIT))
|
|
||||||
val totalpayColumnIndex = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TOTALPAY))
|
|
||||||
totalDuration = totalDuration + durationColumnIndex
|
|
||||||
if (typeColumnIndex.contains("Hourly")) {
|
|
||||||
countOfTypeH = countOfTypeH + 1
|
|
||||||
} else if (typeColumnIndex.contains("Piece")) {
|
|
||||||
countOfTypeP = countOfTypeP + 1
|
|
||||||
}
|
|
||||||
totalUnits = totalUnits + unitColumnIndex
|
|
||||||
totalPay = totalPay + totalpayColumnIndex
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (cursor != null && cursor.count > 0) {
|
|
||||||
lines = cursor.count
|
|
||||||
cursor.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buildInfoString(totalDuration, countOfTypeH, countOfTypeP, totalUnits, totalPay, lines)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
|
||||||
fun buildInfoString(totalDuration: Float, countOfTypeH: Int, countOfTypeP: Int, totalUnits: Float, totalPay: Float, lines: Int): String {
|
|
||||||
var textString: String
|
|
||||||
textString = "$lines Shifts"
|
|
||||||
if (countOfTypeH != 0 && countOfTypeP != 0) {
|
|
||||||
textString = "$textString ($countOfTypeH Hourly/$countOfTypeP Piece Rate)"
|
|
||||||
}
|
|
||||||
if (countOfTypeH != 0) {
|
|
||||||
textString = """
|
|
||||||
$textString
|
|
||||||
Total Hours: ${String.format("%.2f", totalDuration)}
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
if (countOfTypeP != 0) {
|
|
||||||
textString = """
|
|
||||||
$textString
|
|
||||||
Total Units: ${String.format("%.2f", totalUnits)}
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
if (totalPay != 0f) {
|
|
||||||
textString = """
|
|
||||||
$textString
|
|
||||||
Total Pay: ${"$"}${String.format("%.2f", totalPay)}
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
return textString
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateLoader(i: Int, bundle: Bundle?): Loader<Cursor> {
|
|
||||||
val projection = arrayOf<String?>(
|
|
||||||
ShiftsEntry._ID,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DATE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TIME_IN,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_BREAK,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DURATION,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TYPE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_PAYRATE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_UNIT,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
|
|
||||||
return CursorLoader(context!!,
|
|
||||||
ShiftsEntry.CONTENT_URI,
|
|
||||||
projection,
|
|
||||||
activity.selection,
|
|
||||||
activity.args,
|
|
||||||
activity.sortOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
|
|
||||||
mCursorAdapter!!.swapCursor(cursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoaderReset(loader: Loader<Cursor>) {
|
|
||||||
mCursorAdapter!!.swapCursor(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
||||||
println("request code$requestCode")
|
|
||||||
if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
|
|
||||||
if (grantResults.size > 0
|
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
exportDialog()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, "Storage Permissions denied", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exportDialog() {
|
|
||||||
AlertDialog.Builder(context)
|
|
||||||
.setTitle("Export?")
|
|
||||||
.setMessage("Exporting current filtered data. Continue?")
|
|
||||||
.setNegativeButton(android.R.string.no, null)
|
|
||||||
.setPositiveButton(android.R.string.yes) { arg0, arg1 -> ExportData() }.create().show()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
|
|
||||||
private const val DEFAULT_LOADER = 0
|
|
||||||
var NEW_LOADER = 0
|
|
||||||
|
|
||||||
// // Storage Permissions
|
|
||||||
// private static final int REQUEST_EXTERNAL_STORAGE = 1;
|
|
||||||
// private static String[] PERMISSIONS_STORAGE = {
|
|
||||||
// Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
||||||
// Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
// };
|
|
||||||
// /**
|
|
||||||
// * Checks if the app has permission to write to device storage
|
|
||||||
// *
|
|
||||||
// * If the app does not has permission then the user will be prompted to grant permissions
|
|
||||||
// *
|
|
||||||
// * @param activity
|
|
||||||
// */
|
|
||||||
// public static void verifyStoragePermissions(Activity activity) {
|
|
||||||
// // Check if we have write permission
|
|
||||||
// int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
|
||||||
//
|
|
||||||
// if (permission != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
// // We don't have permission so prompt the user
|
|
||||||
// ActivityCompat.requestPermissions(
|
|
||||||
// activity,
|
|
||||||
// PERMISSIONS_STORAGE,
|
|
||||||
// REQUEST_EXTERNAL_STORAGE
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
fun checkStoragePermissions(activity: Activity?): Boolean {
|
|
||||||
var status = false
|
|
||||||
val permission = ActivityCompat.checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
if (permission == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
status = true
|
|
||||||
}
|
|
||||||
return status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
package com.appttude.h_mal.farmr
|
|
||||||
|
|
||||||
import android.database.Cursor
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.ProgressBar
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentTransaction
|
|
||||||
import androidx.loader.app.LoaderManager
|
|
||||||
import androidx.loader.content.CursorLoader
|
|
||||||
import androidx.loader.content.Loader
|
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
|
|
||||||
|
|
||||||
class FurtherInfoFragment : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
|
|
||||||
private var typeTV: TextView? = null
|
|
||||||
private var descriptionTV: TextView? = null
|
|
||||||
private var dateTV: TextView? = null
|
|
||||||
private var times: TextView? = null
|
|
||||||
private var breakTV: TextView? = null
|
|
||||||
private var durationTV: TextView? = null
|
|
||||||
private var unitsTV: TextView? = null
|
|
||||||
private var payRateTV: TextView? = null
|
|
||||||
private var totalPayTV: TextView? = null
|
|
||||||
private var hourlyDetailHolder: LinearLayout? = null
|
|
||||||
private var unitsHolder: LinearLayout? = null
|
|
||||||
private var wholeView: LinearLayout? = null
|
|
||||||
private var progressBarFI: ProgressBar? = null
|
|
||||||
private var editButton: Button? = null
|
|
||||||
private var CurrentUri: Uri? = null
|
|
||||||
|
|
||||||
lateinit var activity: MainActivity
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?): View {
|
|
||||||
// Inflate the layout for this fragment
|
|
||||||
val rootView: View = inflater.inflate(R.layout.fragment_futher_info, container, false)
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
activity = (requireActivity() as MainActivity)
|
|
||||||
activity.setActionBarTitle(getString(R.string.further_info_title))
|
|
||||||
|
|
||||||
progressBarFI = rootView.findViewById<View>(R.id.progressBar_info) as ProgressBar?
|
|
||||||
wholeView = rootView.findViewById<View>(R.id.further_info_view) as LinearLayout?
|
|
||||||
typeTV = rootView.findViewById<View>(R.id.details_shift) as TextView?
|
|
||||||
descriptionTV = rootView.findViewById<View>(R.id.details_desc) as TextView?
|
|
||||||
dateTV = rootView.findViewById<View>(R.id.details_date) as TextView?
|
|
||||||
times = rootView.findViewById<View>(R.id.details_time) as TextView?
|
|
||||||
breakTV = rootView.findViewById<View>(R.id.details_breaks) as TextView?
|
|
||||||
durationTV = rootView.findViewById<View>(R.id.details_duration) as TextView?
|
|
||||||
unitsTV = rootView.findViewById<View>(R.id.details_units) as TextView?
|
|
||||||
payRateTV = rootView.findViewById<View>(R.id.details_pay_rate) as TextView?
|
|
||||||
totalPayTV = rootView.findViewById<View>(R.id.details_totalpay) as TextView?
|
|
||||||
editButton = rootView.findViewById<View>(R.id.details_edit) as Button?
|
|
||||||
hourlyDetailHolder = rootView.findViewById<View>(R.id.details_hourly_details) as LinearLayout?
|
|
||||||
unitsHolder = rootView.findViewById<View>(R.id.details_units_holder) as LinearLayout?
|
|
||||||
val b: Bundle? = arguments
|
|
||||||
CurrentUri = Uri.parse(b!!.getString("uri"))
|
|
||||||
loaderManager.initLoader(DEFAULT_LOADER, null, this)
|
|
||||||
editButton!!.setOnClickListener(object : View.OnClickListener {
|
|
||||||
override fun onClick(view: View) {
|
|
||||||
val fragmentTransaction: FragmentTransaction = (activity.fragmentManager)!!.beginTransaction()
|
|
||||||
val fragment: Fragment = FragmentAddItem()
|
|
||||||
fragment.arguments = b
|
|
||||||
fragmentTransaction.replace(R.id.container, fragment).addToBackStack("additem").commit()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return rootView
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater)
|
|
||||||
menu.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor?> {
|
|
||||||
progressBarFI!!.visibility = View.VISIBLE
|
|
||||||
wholeView!!.visibility = View.GONE
|
|
||||||
val projection: Array<String?> = arrayOf(
|
|
||||||
ShiftsEntry._ID,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DESCRIPTION,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DATE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TIME_IN,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TIME_OUT,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_BREAK,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_DURATION,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TYPE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_PAYRATE,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_UNIT,
|
|
||||||
ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
|
|
||||||
return CursorLoader((context)!!, (CurrentUri)!!,
|
|
||||||
projection, null, null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
|
|
||||||
progressBarFI!!.visibility = View.GONE
|
|
||||||
wholeView!!.visibility = View.VISIBLE
|
|
||||||
if (cursor == null || cursor.count < 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (cursor.moveToFirst()) {
|
|
||||||
val descriptionColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)
|
|
||||||
val dateColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DATE)
|
|
||||||
val timeInColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TIME_IN)
|
|
||||||
val timeOutColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)
|
|
||||||
val breakColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_BREAK)
|
|
||||||
val durationColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_DURATION)
|
|
||||||
val typeColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TYPE)
|
|
||||||
val unitColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_UNIT)
|
|
||||||
val payrateColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_PAYRATE)
|
|
||||||
val totalPayColumnIndex: Int = cursor.getColumnIndex(ShiftsEntry.COLUMN_SHIFT_TOTALPAY)
|
|
||||||
val type: String = cursor.getString(typeColumnIndex)
|
|
||||||
val description: String = cursor.getString(descriptionColumnIndex)
|
|
||||||
val date: String = cursor.getString(dateColumnIndex)
|
|
||||||
val timeIn: String = cursor.getString(timeInColumnIndex)
|
|
||||||
val timeOut: String = cursor.getString(timeOutColumnIndex)
|
|
||||||
val breaks: Int = cursor.getInt(breakColumnIndex)
|
|
||||||
val duration: Float = cursor.getFloat(durationColumnIndex)
|
|
||||||
val unit: Float = cursor.getFloat(unitColumnIndex)
|
|
||||||
val payrate: Float = cursor.getFloat(payrateColumnIndex)
|
|
||||||
val totalPay: Float = cursor.getFloat(totalPayColumnIndex)
|
|
||||||
var durationString: String = ShiftsCursorAdapter.Companion.timeValues(duration).get(0) + " Hours " + ShiftsCursorAdapter.Companion.timeValues(duration).get(1) + " Minutes "
|
|
||||||
if (breaks != 0) {
|
|
||||||
durationString = durationString + " (+ " + Integer.toString(breaks) + " minutes break)"
|
|
||||||
}
|
|
||||||
typeTV!!.text = type
|
|
||||||
descriptionTV!!.text = description
|
|
||||||
dateTV!!.text = date
|
|
||||||
var totalPaid: String? = ""
|
|
||||||
val currency: String = "$"
|
|
||||||
if ((type == "Hourly")) {
|
|
||||||
hourlyDetailHolder!!.visibility = View.VISIBLE
|
|
||||||
unitsHolder!!.visibility = View.GONE
|
|
||||||
times!!.text = timeIn + " - " + timeOut
|
|
||||||
breakTV!!.text = Integer.toString(breaks) + "mins"
|
|
||||||
durationTV!!.text = durationString
|
|
||||||
totalPaid = (String.format("%.2f", duration) + " Hours @ " + currency + String.format("%.2f", payrate) + " per Hour" + "\n"
|
|
||||||
+ "Equals: " + currency + String.format("%.2f", totalPay))
|
|
||||||
} else if ((type == "Piece Rate")) {
|
|
||||||
hourlyDetailHolder!!.visibility = View.GONE
|
|
||||||
unitsHolder!!.visibility = View.VISIBLE
|
|
||||||
unitsTV!!.text = String.format("%.2f", unit)
|
|
||||||
totalPaid = (String.format("%.2f", unit) + " Units @ " + currency + String.format("%.2f", payrate) + " per Unit" + "\n"
|
|
||||||
+ "Equals: " + currency + String.format("%.2f", totalPay))
|
|
||||||
}
|
|
||||||
payRateTV!!.text = String.format("%.2f", payrate)
|
|
||||||
totalPayTV!!.text = totalPaid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoaderReset(loader: Loader<Cursor>) {}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val DEFAULT_LOADER: Int = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package com.appttude.h_mal.farmr
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.View
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.appcompat.widget.Toolbar
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.fragment.app.FragmentManager
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
|
||||||
var filter: SharedPreferences? = null
|
|
||||||
var context: Context? = null
|
|
||||||
var sortOrder: String? = null
|
|
||||||
var selection: String? = null
|
|
||||||
var args: Array<String>? = null
|
|
||||||
private var toolbar: Toolbar? = null
|
|
||||||
var fragmentManager: FragmentManager? = null
|
|
||||||
private var currentFragment: String? = null
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.main_view)
|
|
||||||
verifyStoragePermissions(this)
|
|
||||||
toolbar = findViewById<View>(R.id.toolbar) as Toolbar
|
|
||||||
setSupportActionBar(toolbar)
|
|
||||||
fragmentManager = supportFragmentManager
|
|
||||||
val fragmentTransaction = fragmentManager?.beginTransaction()
|
|
||||||
fragmentTransaction?.replace(R.id.container, FragmentMain())?.addToBackStack("main")?.commit()
|
|
||||||
fragmentManager?.addOnBackStackChangedListener {
|
|
||||||
val f = fragmentManager?.fragments
|
|
||||||
val frag = f?.get(0)
|
|
||||||
currentFragment = frag?.javaClass?.simpleName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
|
||||||
menuInflater.inflate(R.menu.menu_main, menu)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBackPressed() {
|
|
||||||
when (currentFragment) {
|
|
||||||
"FragmentMain" -> {
|
|
||||||
AlertDialog.Builder(this)
|
|
||||||
.setTitle("Leave?")
|
|
||||||
.setMessage("Are you sure you want to exit Farmr?")
|
|
||||||
.setNegativeButton(android.R.string.no, null)
|
|
||||||
.setPositiveButton(android.R.string.yes) { arg0, arg1 ->
|
|
||||||
val intent = Intent(Intent.ACTION_MAIN)
|
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
|
|
||||||
intent.addCategory(Intent.CATEGORY_HOME)
|
|
||||||
startActivity(intent)
|
|
||||||
finish()
|
|
||||||
System.exit(0)
|
|
||||||
}.create().show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
"FragmentAddItem" -> {
|
|
||||||
if (FragmentAddItem.Companion.mRadioGroup!!.checkedRadioButtonId == -1) {
|
|
||||||
fragmentManager!!.popBackStack()
|
|
||||||
} else {
|
|
||||||
AlertDialog.Builder(this)
|
|
||||||
.setTitle("Discard Changes?")
|
|
||||||
.setMessage("Are you sure you want to discard changes?")
|
|
||||||
.setNegativeButton(android.R.string.no, null)
|
|
||||||
.setPositiveButton(android.R.string.yes) { arg0, arg1 -> fragmentManager!!.popBackStack() }.create().show()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> if (fragmentManager!!.backStackEntryCount > 1) {
|
|
||||||
fragmentManager!!.popBackStack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setActionBarTitle(title: String?) {
|
|
||||||
toolbar!!.title = title
|
|
||||||
}
|
|
||||||
|
|
||||||
// Storage Permissions
|
|
||||||
private val REQUEST_EXTERNAL_STORAGE = 1
|
|
||||||
private val PERMISSIONS_STORAGE = arrayOf(
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the app has permission to write to device storage
|
|
||||||
*
|
|
||||||
* If the app does not has permission then the user will be prompted to grant permissions
|
|
||||||
*
|
|
||||||
* @param activity
|
|
||||||
*/
|
|
||||||
fun verifyStoragePermissions(activity: Activity?) {
|
|
||||||
// Check if we have write permission
|
|
||||||
val permission = ActivityCompat.checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
// We don't have permission so prompt the user
|
|
||||||
ActivityCompat.requestPermissions(
|
|
||||||
activity,
|
|
||||||
PERMISSIONS_STORAGE,
|
|
||||||
REQUEST_EXTERNAL_STORAGE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package com.appttude.h_mal.farmr
|
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.ContentUris
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.database.Cursor
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.View.OnLongClickListener
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.CursorAdapter
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.fragment.app.FragmentTransaction
|
|
||||||
import com.appttude.h_mal.farmr.data.ShiftProvider
|
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
|
|
||||||
import kotlin.math.floor
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by h_mal on 26/12/2017.
|
|
||||||
*/
|
|
||||||
class ShiftsCursorAdapter constructor(private val activity: MainActivity, c: Cursor?) : CursorAdapter(activity, c, 0) {
|
|
||||||
private var mContext: Context? = null
|
|
||||||
var shiftProvider: ShiftProvider? = null
|
|
||||||
override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
|
|
||||||
return LayoutInflater.from(context).inflate(R.layout.list_item_1, parent, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindView(view: View, context: Context, cursor: Cursor) {
|
|
||||||
mContext = context
|
|
||||||
val descriptionTextView: TextView = view.findViewById<View>(R.id.location) as TextView
|
|
||||||
val dateTextView: TextView = view.findViewById<View>(R.id.date) as TextView
|
|
||||||
val totalPay: TextView = view.findViewById<View>(R.id.total_pay) as TextView
|
|
||||||
val hoursView: TextView = view.findViewById<View>(R.id.hours) as TextView
|
|
||||||
val h: TextView = view.findViewById<View>(R.id.h) as TextView
|
|
||||||
val minutesView: TextView = view.findViewById<View>(R.id.minutes) as TextView
|
|
||||||
val m: TextView = view.findViewById<View>(R.id.m) as TextView
|
|
||||||
val editView: ImageView = view.findViewById<View>(R.id.imageView) as ImageView
|
|
||||||
h.text = "h"
|
|
||||||
m.text = "m"
|
|
||||||
val typeColumnIndex: String = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TYPE))
|
|
||||||
val descriptionColumnIndex: String = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION))
|
|
||||||
val dateColumnIndex: String = cursor.getString(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DATE))
|
|
||||||
val durationColumnIndex: Float = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_DURATION))
|
|
||||||
val unitsColumnIndex: Float = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_UNIT))
|
|
||||||
val totalpayColumnIndex: Float = cursor.getFloat(cursor.getColumnIndexOrThrow(ShiftsEntry.COLUMN_SHIFT_TOTALPAY))
|
|
||||||
descriptionTextView.text = descriptionColumnIndex
|
|
||||||
dateTextView.text = newDate(dateColumnIndex)
|
|
||||||
totalPay.text = String.format("%.2f", totalpayColumnIndex)
|
|
||||||
if ((typeColumnIndex == "Piece Rate") && durationColumnIndex == 0f) {
|
|
||||||
hoursView.text = unitsColumnIndex.toString()
|
|
||||||
h.text = ""
|
|
||||||
minutesView.text = ""
|
|
||||||
m.text = "pcs"
|
|
||||||
} else // if(typeColumnIndex.equals("Hourly") || typeColumnIndex.equals("hourly"))
|
|
||||||
{
|
|
||||||
hoursView.text = timeValues(durationColumnIndex).get(0)
|
|
||||||
minutesView.text = timeValues(durationColumnIndex).get(1)
|
|
||||||
}
|
|
||||||
val ID: Long = cursor.getLong(cursor.getColumnIndexOrThrow(ShiftsEntry._ID))
|
|
||||||
val currentProductUri: Uri = ContentUris.withAppendedId(ShiftsEntry.CONTENT_URI, ID)
|
|
||||||
val b: Bundle = Bundle()
|
|
||||||
b.putString("uri", currentProductUri.toString())
|
|
||||||
view.setOnClickListener { // activity.clickOnViewItem(ID);
|
|
||||||
val fragmentTransaction: FragmentTransaction = (activity.fragmentManager)!!.beginTransaction()
|
|
||||||
val fragment2: FurtherInfoFragment = FurtherInfoFragment()
|
|
||||||
fragment2.arguments = b
|
|
||||||
fragmentTransaction.replace(R.id.container, fragment2).addToBackStack("furtherinfo").commit()
|
|
||||||
}
|
|
||||||
editView.setOnClickListener {
|
|
||||||
val fragmentTransaction: FragmentTransaction = (activity.fragmentManager)!!.beginTransaction()
|
|
||||||
val fragment3: FragmentAddItem = FragmentAddItem()
|
|
||||||
fragment3.arguments = b
|
|
||||||
fragmentTransaction.replace(R.id.container, fragment3).addToBackStack("additem").commit()
|
|
||||||
}
|
|
||||||
view.setOnLongClickListener {
|
|
||||||
println("long click: $ID")
|
|
||||||
val builder: AlertDialog.Builder = AlertDialog.Builder(mContext)
|
|
||||||
builder.setMessage("Are you sure you want to delete")
|
|
||||||
builder.setPositiveButton("delete") { dialog, id -> deleteProduct(ID) }
|
|
||||||
builder.setNegativeButton("cancel") { dialog, id ->
|
|
||||||
dialog?.dismiss()
|
|
||||||
}
|
|
||||||
val alertDialog: AlertDialog = builder.create()
|
|
||||||
alertDialog.show()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteProduct(id: Long) {
|
|
||||||
val args: Array<String> = arrayOf(id.toString())
|
|
||||||
// String whereClause = String.format(ShiftsEntry._ID + " in (%s)", new Object[] { TextUtils.join(",", Collections.nCopies(args.length, "?")) }); //for deleting multiple lines
|
|
||||||
mContext!!.contentResolver.delete(ShiftsEntry.CONTENT_URI, ShiftsEntry._ID + "=?", args)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newDate(dateString: String): String {
|
|
||||||
var returnString: String? = "01/01/2010"
|
|
||||||
val year: String = dateString.substring(0, 4)
|
|
||||||
val month: String = dateString.substring(5, 7)
|
|
||||||
val day: String = dateString.substring(8)
|
|
||||||
returnString = "$day-$month-$year"
|
|
||||||
return returnString
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun timeValues(duration: Float): Array<String> {
|
|
||||||
val hours: Int = floor(duration.toDouble()).toInt()
|
|
||||||
val minutes: Int = ((duration - hours) * 60).toInt()
|
|
||||||
val hoursString: String = hours.toString() + ""
|
|
||||||
val minutesString: String = String.format("%02d", minutes)
|
|
||||||
return arrayOf(hoursString, minutesString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.appttude.h_mal.farmr.base
|
||||||
|
|
||||||
|
interface BackPressedListener {
|
||||||
|
fun onBackPressed(): Boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.appttude.h_mal.farmr.base
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.appttude.h_mal.farmr.utils.displayToast
|
||||||
|
|
||||||
|
abstract class BaseActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a loading view which to be shown during async operations
|
||||||
|
*
|
||||||
|
* #setOnClickListener(null) is an ugly work around to prevent under being clicked during
|
||||||
|
* loading
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun <A : AppCompatActivity> startActivity(activity: Class<A>) {
|
||||||
|
val intent = Intent(this, activity)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in case of success or some data emitted from the liveData in viewModel
|
||||||
|
*/
|
||||||
|
open fun onStarted() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in case of success or some data emitted from the liveData in viewModel
|
||||||
|
*/
|
||||||
|
open fun onSuccess(data: Any?) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in case of failure or some error emitted from the liveData in viewModel
|
||||||
|
*/
|
||||||
|
open fun onFailure(error: Any?) {
|
||||||
|
if (error is String) displayToast(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTitleInActionBar(title: String) {
|
||||||
|
supportActionBar?.title = title
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.appttude.h_mal.farmr.base
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.appttude.h_mal.farmr.data.RepositoryImpl
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.LegacyDatabase
|
||||||
|
import com.appttude.h_mal.farmr.data.prefs.PreferenceProvider
|
||||||
|
import com.appttude.h_mal.farmr.viewmodel.ApplicationViewModelFactory
|
||||||
|
import org.kodein.di.Kodein
|
||||||
|
import org.kodein.di.KodeinAware
|
||||||
|
import org.kodein.di.android.x.androidXModule
|
||||||
|
import org.kodein.di.generic.bind
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
import org.kodein.di.generic.provider
|
||||||
|
import org.kodein.di.generic.singleton
|
||||||
|
|
||||||
|
abstract class BaseApplication() : Application(), KodeinAware {
|
||||||
|
|
||||||
|
// Kodein creation of modules to be retrieve within the app
|
||||||
|
override val kodein = Kodein.lazy {
|
||||||
|
import(androidXModule(this@BaseApplication))
|
||||||
|
|
||||||
|
bind() from singleton { createDatabase() }
|
||||||
|
bind() from singleton { createPrefs() }
|
||||||
|
bind() from singleton { RepositoryImpl(instance(), instance()) }
|
||||||
|
|
||||||
|
bind() from provider { ApplicationViewModelFactory(instance()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun createDatabase(): LegacyDatabase
|
||||||
|
abstract fun createPrefs(): PreferenceProvider
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.appttude.h_mal.farmr.base
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelLazy
|
||||||
|
import com.appttude.h_mal.farmr.model.ViewState
|
||||||
|
import com.appttude.h_mal.farmr.utils.getGenericClassAt
|
||||||
|
import com.appttude.h_mal.farmr.utils.popBackStack
|
||||||
|
import com.appttude.h_mal.farmr.viewmodel.ApplicationViewModelFactory
|
||||||
|
import org.kodein.di.KodeinAware
|
||||||
|
import org.kodein.di.android.x.kodein
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
@Suppress("EmptyMethod", "EmptyMethod")
|
||||||
|
abstract class BaseFragment<V : BaseViewModel>(@LayoutRes contentLayoutId: Int) :
|
||||||
|
Fragment(contentLayoutId), KodeinAware {
|
||||||
|
|
||||||
|
override val kodein by kodein()
|
||||||
|
private val factory by instance<ApplicationViewModelFactory>()
|
||||||
|
|
||||||
|
val viewModel: V by getViewModel()
|
||||||
|
|
||||||
|
private fun getViewModel(): Lazy<V> =
|
||||||
|
ViewModelLazy(getGenericClassAt(0), storeProducer = { viewModelStore },
|
||||||
|
factoryProducer = { factory } )
|
||||||
|
|
||||||
|
var mActivity: BaseActivity? = null
|
||||||
|
|
||||||
|
private var shortAnimationDuration by Delegates.notNull<Int>()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
mActivity = requireActivity() as BaseActivity
|
||||||
|
configureObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureObserver() {
|
||||||
|
viewModel.uiState.observe(viewLifecycleOwner) {
|
||||||
|
when (it) {
|
||||||
|
is ViewState.HasStarted -> onStarted()
|
||||||
|
is ViewState.HasData<*> -> onSuccess(it.data)
|
||||||
|
is ViewState.HasError<*> -> onFailure(it.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in case of starting operation liveData in viewModel
|
||||||
|
*/
|
||||||
|
open fun onStarted() {
|
||||||
|
mActivity?.onStarted()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in case of success or some data emitted from the liveData in viewModel
|
||||||
|
*/
|
||||||
|
open fun onSuccess(data: Any?) {
|
||||||
|
mActivity?.onSuccess(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in case of failure or some error emitted from the liveData in viewModel
|
||||||
|
*/
|
||||||
|
open fun onFailure(error: Any?) {
|
||||||
|
mActivity?.onFailure(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTitle(title: String) {
|
||||||
|
(requireActivity() as BaseActivity).setTitleInActionBar(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun popBackStack() = mActivity?.popBackStack()
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.appttude.h_mal.farmr.base
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
|
import com.appttude.h_mal.farmr.utils.generateView
|
||||||
|
|
||||||
|
open class BaseRecyclerAdapter<T: Any>(
|
||||||
|
@LayoutRes private val emptyViewId: Int,
|
||||||
|
@LayoutRes private val currentViewId: Int
|
||||||
|
): RecyclerView.Adapter<ViewHolder>() {
|
||||||
|
var list: List<T>? = null
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
return if (list.isNullOrEmpty()) {
|
||||||
|
val emptyViewHolder = parent.generateView(emptyViewId)
|
||||||
|
EmptyViewHolder(emptyViewHolder)
|
||||||
|
} else {
|
||||||
|
val currentViewHolder = parent.generateView(currentViewId)
|
||||||
|
CurrentViewHolder(currentViewHolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return if (list.isNullOrEmpty()) 1 else list!!.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is EmptyViewHolder -> bindEmptyView(holder.itemView)
|
||||||
|
is CurrentViewHolder -> bindCurrentView(holder.itemView, position, list!![position])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun bindEmptyView(view: View) {}
|
||||||
|
open fun bindCurrentView(view: View, position: Int, data: T) {}
|
||||||
|
|
||||||
|
class EmptyViewHolder(itemView: View): ViewHolder(itemView)
|
||||||
|
class CurrentViewHolder(itemView: View): ViewHolder(itemView)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.appttude.h_mal.farmr.base
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.appttude.h_mal.farmr.model.ViewState
|
||||||
|
|
||||||
|
open class BaseViewModel: ViewModel() {
|
||||||
|
|
||||||
|
private val _uiState = MutableLiveData<ViewState>()
|
||||||
|
val uiState: LiveData<ViewState> = _uiState
|
||||||
|
|
||||||
|
|
||||||
|
fun onStart() {
|
||||||
|
_uiState.postValue(ViewState.HasStarted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : Any> onSuccess(result: T) {
|
||||||
|
_uiState.postValue(ViewState.HasData(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun <E : Any> onError(error: E) {
|
||||||
|
_uiState.postValue(ViewState.HasError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.appttude.h_mal.farmr.data
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
|
||||||
|
import com.appttude.h_mal.farmr.model.Order
|
||||||
|
import com.appttude.h_mal.farmr.model.Shift
|
||||||
|
import com.appttude.h_mal.farmr.model.Sortable
|
||||||
|
|
||||||
|
interface Repository {
|
||||||
|
fun insertShiftIntoDatabase(shift: Shift): Boolean
|
||||||
|
fun updateShiftIntoDatabase(id: Long, shift: Shift): Boolean
|
||||||
|
fun readShiftsFromDatabase(): List<ShiftObject>?
|
||||||
|
fun readSingleShiftFromDatabase(id: Long): ShiftObject?
|
||||||
|
fun deleteSingleShiftFromDatabase(id: Long): Boolean
|
||||||
|
fun deleteAllShiftsFromDatabase(): Boolean
|
||||||
|
fun retrieveSortAndOrderFromPref(): Pair<Sortable?, Order?>
|
||||||
|
fun setSortAndOrderFromPref(sortable: Sortable, order: Order)
|
||||||
|
fun retrieveFilteringDetailsInPrefs(): Map<String, String?>
|
||||||
|
fun setFilteringDetailsInPrefs(
|
||||||
|
description: String?,
|
||||||
|
timeIn: String?,
|
||||||
|
timeOut: String?,
|
||||||
|
type: String?
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.appttude.h_mal.farmr.data
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.LegacyDatabase
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
|
||||||
|
import com.appttude.h_mal.farmr.data.prefs.PreferenceProvider
|
||||||
|
import com.appttude.h_mal.farmr.model.Order
|
||||||
|
import com.appttude.h_mal.farmr.model.Shift
|
||||||
|
import com.appttude.h_mal.farmr.model.Sortable
|
||||||
|
|
||||||
|
class RepositoryImpl(
|
||||||
|
private val legacyDatabase: LegacyDatabase,
|
||||||
|
private val preferenceProvider: PreferenceProvider
|
||||||
|
): Repository {
|
||||||
|
override fun insertShiftIntoDatabase(shift: Shift): Boolean {
|
||||||
|
return legacyDatabase.insertShiftDataIntoDatabase(shift) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateShiftIntoDatabase(id: Long, shift: Shift): Boolean {
|
||||||
|
return legacyDatabase.updateShiftDataIntoDatabase(
|
||||||
|
id = id,
|
||||||
|
typeString = shift.type.type,
|
||||||
|
descriptionString = shift.description,
|
||||||
|
dateString = shift.date,
|
||||||
|
timeInString = shift.timeIn ?: "",
|
||||||
|
timeOutString = shift.timeOut ?: "",
|
||||||
|
duration = shift.duration ?: 0f,
|
||||||
|
breaks = shift.breakMins ?: 0,
|
||||||
|
units = shift.units ?: 0f,
|
||||||
|
payRate = shift.rateOfPay,
|
||||||
|
totalPay = shift.totalPay
|
||||||
|
) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readShiftsFromDatabase(): List<ShiftObject>? {
|
||||||
|
return legacyDatabase.readShiftsFromDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun readSingleShiftFromDatabase(id: Long): ShiftObject? {
|
||||||
|
return legacyDatabase.readSingleShiftWithId(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteSingleShiftFromDatabase(id: Long): Boolean {
|
||||||
|
return legacyDatabase.deleteSingleShift(id) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteAllShiftsFromDatabase(): Boolean {
|
||||||
|
return legacyDatabase.deleteAllShiftsInDatabase() > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun retrieveSortAndOrderFromPref(): Pair<Sortable?, Order?> {
|
||||||
|
return preferenceProvider.getSortableAndOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setSortAndOrderFromPref(sortable: Sortable, order: Order) {
|
||||||
|
preferenceProvider.saveSortableAndOrder(sortable, order)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun retrieveFilteringDetailsInPrefs(): Map<String, String?> {
|
||||||
|
return preferenceProvider.getFilteringDetails()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setFilteringDetailsInPrefs(
|
||||||
|
description: String?,
|
||||||
|
timeIn: String?,
|
||||||
|
timeOut: String?,
|
||||||
|
type: String?
|
||||||
|
) {
|
||||||
|
preferenceProvider.saveFilteringDetails(description, timeIn, timeOut, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package com.appttude.h_mal.farmr.data.legacydb
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.ContentUris
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_BREAK
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DATE
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DURATION
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_PAYRATE
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_IN
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_OUT
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TOTALPAY
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TYPE
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_UNIT
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.CONTENT_URI
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry._ID
|
||||||
|
import com.appttude.h_mal.farmr.model.Shift
|
||||||
|
|
||||||
|
class LegacyDatabase(private val resolver: ContentResolver) {
|
||||||
|
|
||||||
|
private val projection = arrayOf<String?>(
|
||||||
|
_ID,
|
||||||
|
COLUMN_SHIFT_DESCRIPTION,
|
||||||
|
COLUMN_SHIFT_DATE,
|
||||||
|
COLUMN_SHIFT_TIME_IN,
|
||||||
|
COLUMN_SHIFT_TIME_OUT,
|
||||||
|
COLUMN_SHIFT_BREAK,
|
||||||
|
COLUMN_SHIFT_DURATION,
|
||||||
|
COLUMN_SHIFT_TYPE,
|
||||||
|
COLUMN_SHIFT_UNIT,
|
||||||
|
COLUMN_SHIFT_PAYRATE,
|
||||||
|
COLUMN_SHIFT_TOTALPAY
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create
|
||||||
|
fun insertShiftDataIntoDatabase(
|
||||||
|
shift: Shift
|
||||||
|
): Uri? {
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(COLUMN_SHIFT_TYPE, shift.type.type)
|
||||||
|
put(COLUMN_SHIFT_DESCRIPTION, shift.description)
|
||||||
|
put(COLUMN_SHIFT_DATE, shift.date)
|
||||||
|
put(COLUMN_SHIFT_TIME_IN, shift.timeIn ?: "00:00")
|
||||||
|
put(COLUMN_SHIFT_TIME_OUT, shift.timeOut ?: "00:00")
|
||||||
|
put(COLUMN_SHIFT_DURATION, shift.duration ?: 0.00f)
|
||||||
|
put(COLUMN_SHIFT_BREAK, shift.breakMins ?: 0)
|
||||||
|
put(COLUMN_SHIFT_UNIT, shift.units ?: 0.00f)
|
||||||
|
put(COLUMN_SHIFT_PAYRATE, shift.rateOfPay)
|
||||||
|
put(COLUMN_SHIFT_TOTALPAY, shift.totalPay)
|
||||||
|
}
|
||||||
|
return resolver.insert(CONTENT_URI, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read
|
||||||
|
fun readShiftsFromDatabase(): List<ShiftObject>? {
|
||||||
|
val cursor = resolver.query(
|
||||||
|
CONTENT_URI,
|
||||||
|
projection,
|
||||||
|
null, null, null
|
||||||
|
) ?: return null
|
||||||
|
val shifts = generateSequence { if (cursor.moveToNext()) cursor else null }
|
||||||
|
.map { it.getShift() }
|
||||||
|
.toList()
|
||||||
|
// close cursor after query operations
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
return shifts
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readSingleShiftWithId(id: Long): ShiftObject? {
|
||||||
|
val itemUri: Uri = ContentUris.withAppendedId(CONTENT_URI, id)
|
||||||
|
|
||||||
|
val cursor = resolver.query(
|
||||||
|
itemUri,
|
||||||
|
projection,
|
||||||
|
null, null, null
|
||||||
|
) ?: return null
|
||||||
|
cursor.moveToFirst()
|
||||||
|
|
||||||
|
val shift = cursor.takeIf { it.moveToFirst() }?.run { getShift() } ?: return null
|
||||||
|
cursor.close()
|
||||||
|
return shift
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update
|
||||||
|
fun updateShiftDataIntoDatabase(
|
||||||
|
id: Long,
|
||||||
|
typeString: String,
|
||||||
|
descriptionString: String,
|
||||||
|
dateString: String,
|
||||||
|
timeInString: String,
|
||||||
|
timeOutString: String,
|
||||||
|
duration: Float,
|
||||||
|
breaks: Int,
|
||||||
|
units: Float,
|
||||||
|
payRate: Float,
|
||||||
|
totalPay: Float,
|
||||||
|
): Int {
|
||||||
|
val itemUri: Uri = ContentUris.withAppendedId(CONTENT_URI, id)
|
||||||
|
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(COLUMN_SHIFT_TYPE, typeString)
|
||||||
|
put(COLUMN_SHIFT_DESCRIPTION, descriptionString)
|
||||||
|
put(COLUMN_SHIFT_DATE, dateString)
|
||||||
|
put(COLUMN_SHIFT_TIME_IN, timeInString)
|
||||||
|
put(COLUMN_SHIFT_TIME_OUT, timeOutString)
|
||||||
|
put(COLUMN_SHIFT_DURATION, duration)
|
||||||
|
put(COLUMN_SHIFT_BREAK, breaks)
|
||||||
|
put(COLUMN_SHIFT_UNIT, units)
|
||||||
|
put(COLUMN_SHIFT_PAYRATE, payRate)
|
||||||
|
put(COLUMN_SHIFT_TOTALPAY, totalPay)
|
||||||
|
}
|
||||||
|
return resolver.update(itemUri, values, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
fun deleteAllShiftsInDatabase(): Int {
|
||||||
|
return resolver.delete(CONTENT_URI, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteSingleShift(id: Long): Int {
|
||||||
|
val args: Array<String> = arrayOf(id.toString())
|
||||||
|
return resolver.delete(CONTENT_URI, "$_ID=?", args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Cursor.getShift(): ShiftObject = run {
|
||||||
|
val id = getLong(getColumnIndexOrThrow(_ID))
|
||||||
|
val descriptionColumnIndex = getString(
|
||||||
|
getColumnIndexOrThrow(
|
||||||
|
COLUMN_SHIFT_DESCRIPTION
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val dateColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_DATE))
|
||||||
|
val timeInColumnIndex =
|
||||||
|
getString(getColumnIndexOrThrow(COLUMN_SHIFT_TIME_IN))
|
||||||
|
val timeOutColumnIndex =
|
||||||
|
getString(getColumnIndexOrThrow(COLUMN_SHIFT_TIME_OUT))
|
||||||
|
val durationColumnIndex =
|
||||||
|
getFloat(getColumnIndexOrThrow(COLUMN_SHIFT_DURATION))
|
||||||
|
val breakOutColumnIndex =
|
||||||
|
getInt(getColumnIndexOrThrow(COLUMN_SHIFT_BREAK))
|
||||||
|
val typeColumnIndex = getString(getColumnIndexOrThrow(COLUMN_SHIFT_TYPE))
|
||||||
|
val unitColumnIndex = getFloat(getColumnIndexOrThrow(COLUMN_SHIFT_UNIT))
|
||||||
|
val payrateColumnIndex =
|
||||||
|
getFloat(getColumnIndexOrThrow(COLUMN_SHIFT_PAYRATE))
|
||||||
|
val totalpayColumnIndex =
|
||||||
|
getFloat(getColumnIndexOrThrow(COLUMN_SHIFT_TOTALPAY))
|
||||||
|
|
||||||
|
ShiftObject(
|
||||||
|
id = id,
|
||||||
|
type = typeColumnIndex,
|
||||||
|
description = descriptionColumnIndex,
|
||||||
|
date = dateColumnIndex,
|
||||||
|
timeIn = timeInColumnIndex,
|
||||||
|
timeOut = timeOutColumnIndex,
|
||||||
|
duration = durationColumnIndex,
|
||||||
|
breakMins = breakOutColumnIndex,
|
||||||
|
units = unitColumnIndex,
|
||||||
|
rateOfPay = payrateColumnIndex,
|
||||||
|
totalPay = totalpayColumnIndex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.appttude.h_mal.farmr.data.legacydb
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.model.Shift
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import kotlin.math.floor
|
||||||
|
|
||||||
|
data class ShiftObject(
|
||||||
|
val id: Long,
|
||||||
|
val type: String,
|
||||||
|
val description: String,
|
||||||
|
val date: String,
|
||||||
|
val timeIn: String,
|
||||||
|
val timeOut: String,
|
||||||
|
val duration: Float,
|
||||||
|
val breakMins: Int,
|
||||||
|
val units: Float,
|
||||||
|
val rateOfPay: Float,
|
||||||
|
val totalPay: Float
|
||||||
|
) {
|
||||||
|
fun copyToShift() = Shift(ShiftType.getEnumByType(type), description, date, timeIn, timeOut, duration, breakMins, units, rateOfPay, totalPay)
|
||||||
|
|
||||||
|
fun getHoursMinutesPairFromDuration(): Pair<String, String> {
|
||||||
|
val hours: Int = floor(duration).toInt()
|
||||||
|
val minutes: Int = ((duration - hours) * 60).toInt()
|
||||||
|
return Pair(hours.toString(), minutes.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
package com.appttude.h_mal.farmr.data
|
package com.appttude.h_mal.farmr.data.legacydb
|
||||||
|
|
||||||
import android.content.ContentProvider
|
import android.content.ContentProvider
|
||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
|
||||||
import android.content.UriMatcher
|
import android.content.UriMatcher
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.VisibleForTesting
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by h_mal on 26/12/2017.
|
* Created by h_mal on 26/12/2017.
|
||||||
@@ -21,22 +19,24 @@ class ShiftProvider : ContentProvider() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?,
|
override fun query(
|
||||||
sortOrder: String?): Cursor? {
|
uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?,
|
||||||
var selection = selection
|
sortOrder: String?
|
||||||
var selectionArgs = selectionArgs
|
): Cursor {
|
||||||
val database = mDbHelper!!.readableDatabase
|
val database = mDbHelper!!.readableDatabase
|
||||||
val cursor: Cursor
|
val cursor: Cursor = when (sUriMatcher.match(uri)) {
|
||||||
val match = sUriMatcher.match(uri)
|
SHIFTS -> database.query(
|
||||||
when (match) {
|
ShiftsEntry.TABLE_NAME, projection, selection, selectionArgs,
|
||||||
SHIFTS -> cursor = database.query(ShiftsEntry.TABLE_NAME, projection, selection, selectionArgs,
|
null, null, sortOrder
|
||||||
null, null, sortOrder)
|
)
|
||||||
|
|
||||||
SHIFT_ID -> {
|
SHIFT_ID -> {
|
||||||
selection = ShiftsEntry._ID + "=?"
|
val mSelection = ShiftsEntry._ID + "=?"
|
||||||
selectionArgs = arrayOf(ContentUris.parseId(uri).toString())
|
val mSelectionArgs = arrayOf(ContentUris.parseId(uri).toString())
|
||||||
cursor = database.query(ShiftsEntry.TABLE_NAME, projection, selection, selectionArgs,
|
database.query(
|
||||||
null, null, sortOrder)
|
ShiftsEntry.TABLE_NAME, projection, mSelection, mSelectionArgs,
|
||||||
|
null, null, sortOrder
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("Cannot query $uri")
|
else -> throw IllegalArgumentException("Cannot query $uri")
|
||||||
@@ -46,25 +46,24 @@ class ShiftProvider : ContentProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
|
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
|
||||||
val match = sUriMatcher.match(uri)
|
return when (sUriMatcher.match(uri)) {
|
||||||
return when (match) {
|
|
||||||
SHIFTS -> insertShift(uri, contentValues)
|
SHIFTS -> insertShift(uri, contentValues)
|
||||||
else -> throw IllegalArgumentException("Insertion is not supported for $uri")
|
else -> throw IllegalArgumentException("Insertion is not supported for $uri")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun insertShift(uri: Uri, values: ContentValues?): Uri? {
|
private fun insertShift(uri: Uri, values: ContentValues?): Uri? {
|
||||||
val description = values!!.getAsString(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)
|
values!!.getAsString(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)
|
||||||
?: throw IllegalArgumentException("Description required")
|
?: throw IllegalArgumentException("Description required")
|
||||||
val date = values.getAsString(ShiftsEntry.COLUMN_SHIFT_DATE)
|
values.getAsString(ShiftsEntry.COLUMN_SHIFT_DATE)
|
||||||
?: throw IllegalArgumentException("Date required")
|
?: throw IllegalArgumentException("Date required")
|
||||||
val timeIn = values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_IN)
|
values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_IN)
|
||||||
?: throw IllegalArgumentException("Time In required")
|
?: throw IllegalArgumentException("Time In required")
|
||||||
val timeOut = values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)
|
values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)
|
||||||
?: throw IllegalArgumentException("Time Out required")
|
?: throw IllegalArgumentException("Time Out required")
|
||||||
val duration = values.getAsFloat(ShiftsEntry.COLUMN_SHIFT_DURATION)
|
val duration = values.getAsFloat(ShiftsEntry.COLUMN_SHIFT_DURATION)
|
||||||
require(duration >= 0) { "Duration cannot be negative" }
|
require(duration >= 0) { "Duration cannot be negative" }
|
||||||
val shiftType = values.getAsString(ShiftsEntry.COLUMN_SHIFT_TYPE)
|
values.getAsString(ShiftsEntry.COLUMN_SHIFT_TYPE)
|
||||||
?: throw IllegalArgumentException("Shift type required")
|
?: throw IllegalArgumentException("Shift type required")
|
||||||
val shiftUnits = values.getAsFloat(ShiftsEntry.COLUMN_SHIFT_UNIT)
|
val shiftUnits = values.getAsFloat(ShiftsEntry.COLUMN_SHIFT_UNIT)
|
||||||
require(shiftUnits >= 0) { "Units cannot be negative" }
|
require(shiftUnits >= 0) { "Units cannot be negative" }
|
||||||
@@ -84,42 +83,46 @@ class ShiftProvider : ContentProvider() {
|
|||||||
return ContentUris.withAppendedId(uri, id)
|
return ContentUris.withAppendedId(uri, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(uri: Uri, contentValues: ContentValues?, selection: String?,
|
override fun update(
|
||||||
selectionArgs: Array<String>?): Int {
|
uri: Uri, contentValues: ContentValues?, selection: String?,
|
||||||
var selection = selection
|
selectionArgs: Array<String>?
|
||||||
var selectionArgs = selectionArgs
|
): Int {
|
||||||
val match = sUriMatcher.match(uri)
|
return when (sUriMatcher.match(uri)) {
|
||||||
return when (match) {
|
|
||||||
SHIFTS -> updateShift(uri, contentValues, selection, selectionArgs)
|
SHIFTS -> updateShift(uri, contentValues, selection, selectionArgs)
|
||||||
SHIFT_ID -> {
|
SHIFT_ID -> {
|
||||||
selection = ShiftsEntry._ID + "=?"
|
val mSelection = ShiftsEntry._ID + "=?"
|
||||||
selectionArgs = arrayOf(ContentUris.parseId(uri).toString())
|
val mSelectionArgs = arrayOf(ContentUris.parseId(uri).toString())
|
||||||
updateShift(uri, contentValues, selection, selectionArgs)
|
updateShift(uri, contentValues, mSelection, mSelectionArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("Update is not supported for $uri")
|
else -> throw IllegalArgumentException("Update is not supported for $uri")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateShift(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
|
private fun updateShift(
|
||||||
|
uri: Uri,
|
||||||
|
values: ContentValues?,
|
||||||
|
selection: String?,
|
||||||
|
selectionArgs: Array<String>?
|
||||||
|
): Int {
|
||||||
if (values!!.containsKey(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)) {
|
if (values!!.containsKey(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)) {
|
||||||
val description = values.getAsString(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)
|
values.getAsString(ShiftsEntry.COLUMN_SHIFT_DESCRIPTION)
|
||||||
?: throw IllegalArgumentException("description required")
|
?: throw IllegalArgumentException("description required")
|
||||||
}
|
}
|
||||||
if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_DATE)) {
|
if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_DATE)) {
|
||||||
val date = values.getAsString(ShiftsEntry.COLUMN_SHIFT_DATE)
|
values.getAsString(ShiftsEntry.COLUMN_SHIFT_DATE)
|
||||||
?: throw IllegalArgumentException("date required")
|
?: throw IllegalArgumentException("date required")
|
||||||
}
|
}
|
||||||
if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_TIME_IN)) {
|
if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_TIME_IN)) {
|
||||||
val timeIn = values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_IN)
|
values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_IN)
|
||||||
?: throw IllegalArgumentException("time in required")
|
?: throw IllegalArgumentException("time in required")
|
||||||
}
|
}
|
||||||
if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)) {
|
if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)) {
|
||||||
val timeOut = values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)
|
values.getAsString(ShiftsEntry.COLUMN_SHIFT_TIME_OUT)
|
||||||
?: throw IllegalArgumentException("time out required")
|
?: throw IllegalArgumentException("time out required")
|
||||||
}
|
}
|
||||||
if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_BREAK)) {
|
if (values.containsKey(ShiftsEntry.COLUMN_SHIFT_BREAK)) {
|
||||||
val breaks = values.getAsString(ShiftsEntry.COLUMN_SHIFT_BREAK)
|
values.getAsString(ShiftsEntry.COLUMN_SHIFT_BREAK)
|
||||||
?: throw IllegalArgumentException("break required")
|
?: throw IllegalArgumentException("break required")
|
||||||
}
|
}
|
||||||
if (values.size() == 0) {
|
if (values.size() == 0) {
|
||||||
@@ -134,17 +137,15 @@ class ShiftProvider : ContentProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
||||||
var selection = selection
|
|
||||||
var selectionArgs = selectionArgs
|
|
||||||
val database = mDbHelper!!.writableDatabase
|
val database = mDbHelper!!.writableDatabase
|
||||||
val rowsDeleted: Int
|
val rowsDeleted: Int = when (sUriMatcher.match(uri)) {
|
||||||
val match = sUriMatcher.match(uri)
|
SHIFTS -> database.delete(ShiftsEntry.TABLE_NAME, selection, selectionArgs)
|
||||||
when (match) {
|
|
||||||
SHIFTS -> rowsDeleted = database.delete(ShiftsEntry.TABLE_NAME, selection, selectionArgs)
|
|
||||||
SHIFT_ID -> {
|
SHIFT_ID -> {
|
||||||
selection = ShiftsEntry._ID + "=?"
|
val mSelection = ShiftsEntry._ID + "=?"
|
||||||
selectionArgs = arrayOf(ContentUris.parseId(uri).toString())
|
val mSelectionArgs = arrayOf(ContentUris.parseId(uri).toString())
|
||||||
rowsDeleted = database.delete(ShiftsEntry.TABLE_NAME, selection, selectionArgs)
|
|
||||||
|
database.delete(ShiftsEntry.TABLE_NAME, mSelection, mSelectionArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("Deletion is not supported for $uri")
|
else -> throw IllegalArgumentException("Deletion is not supported for $uri")
|
||||||
@@ -171,7 +172,11 @@ class ShiftProvider : ContentProvider() {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
sUriMatcher.addURI(ShiftsContract.CONTENT_AUTHORITY, ShiftsContract.PATH_SHIFTS, SHIFTS)
|
sUriMatcher.addURI(ShiftsContract.CONTENT_AUTHORITY, ShiftsContract.PATH_SHIFTS, SHIFTS)
|
||||||
sUriMatcher.addURI(ShiftsContract.CONTENT_AUTHORITY, ShiftsContract.PATH_SHIFTS + "/#", SHIFT_ID)
|
sUriMatcher.addURI(
|
||||||
|
ShiftsContract.CONTENT_AUTHORITY,
|
||||||
|
ShiftsContract.PATH_SHIFTS + "/#",
|
||||||
|
SHIFT_ID
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.appttude.h_mal.farmr.data
|
package com.appttude.h_mal.farmr.data.legacydb
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.appttude.h_mal.farmr.data
|
package com.appttude.h_mal.farmr.data.legacydb
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
import com.appttude.h_mal.farmr.data.ShiftsContract.ShiftsEntry
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by h_mal on 26/12/2017.
|
* Created by h_mal on 26/12/2017.
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.appttude.h_mal.farmr.data.prefs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.appttude.h_mal.farmr.model.Order
|
||||||
|
import com.appttude.h_mal.farmr.model.Sortable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared preferences to save & load last timestamp
|
||||||
|
*/
|
||||||
|
const val SORT = "SORT"
|
||||||
|
const val ORDER = "ORDER"
|
||||||
|
|
||||||
|
const val DESCRIPTION = "DESCRIPTION"
|
||||||
|
const val DATE_IN = "TIME_IN"
|
||||||
|
const val DATE_OUT = "TIME_OUT"
|
||||||
|
const val TYPE = "TYPE"
|
||||||
|
|
||||||
|
class PreferenceProvider(
|
||||||
|
context: Context
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val appContext = context.applicationContext
|
||||||
|
|
||||||
|
private val preference: SharedPreferences
|
||||||
|
get() = PreferenceManager.getDefaultSharedPreferences(appContext)
|
||||||
|
|
||||||
|
fun saveSortableAndOrder(sortable: Sortable, order: Order) {
|
||||||
|
preference.edit()
|
||||||
|
.putString(SORT, sortable.label)
|
||||||
|
.putString(ORDER, order.label)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSortableAndOrder(): Pair<Sortable?, Order?> {
|
||||||
|
val sort = preference.getString(SORT, null)?.let { Sortable.valueOf(it) }
|
||||||
|
val order = preference.getString(ORDER, null)?.let { Order.valueOf(it) }
|
||||||
|
|
||||||
|
return Pair(sort, order)
|
||||||
|
}
|
||||||
|
fun saveFilteringDetails(
|
||||||
|
description: String?,
|
||||||
|
timeIn: String?,
|
||||||
|
timeOut: String?,
|
||||||
|
type: String?
|
||||||
|
) {
|
||||||
|
preference.edit()
|
||||||
|
.putString(DESCRIPTION, description)
|
||||||
|
.putString(DATE_IN, timeIn)
|
||||||
|
.putString(DATE_OUT, timeOut)
|
||||||
|
.putString(TYPE, type)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFilteringDetails(): Map<String, String?> {
|
||||||
|
return mapOf(
|
||||||
|
Pair(DESCRIPTION, preference.getString(DESCRIPTION, null)),
|
||||||
|
Pair(DATE_IN, preference.getString(DATE_IN, null)),
|
||||||
|
Pair(DATE_OUT, preference.getString(DATE_OUT, null)),
|
||||||
|
Pair(TYPE, preference.getString(TYPE, null))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearPrefs() {
|
||||||
|
preference.edit().clear().apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.appttude.h_mal.farmr.di
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.base.BaseApplication
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.LegacyDatabase
|
||||||
|
import com.appttude.h_mal.farmr.data.prefs.PreferenceProvider
|
||||||
|
|
||||||
|
class ShiftApplication: BaseApplication() {
|
||||||
|
|
||||||
|
override fun createDatabase(): LegacyDatabase {
|
||||||
|
return LegacyDatabase(contentResolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createPrefs() = PreferenceProvider(this)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.appttude.h_mal.farmr.model
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class EntityItem(
|
||||||
|
@PrimaryKey(autoGenerate = false)
|
||||||
|
val id: String,
|
||||||
|
val shift: Shift
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.appttude.h_mal.farmr.model
|
||||||
|
|
||||||
|
data class FilterStore(
|
||||||
|
val description: String?,
|
||||||
|
val dateFrom: String?,
|
||||||
|
val dateTo: String?,
|
||||||
|
val type: String?
|
||||||
|
)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.appttude.h_mal.farmr.model
|
||||||
|
|
||||||
|
enum class Order(val label: String) {
|
||||||
|
ASCENDING("Ascending"), DESCENDING("Descending")
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.appttude.h_mal.farmr.model
|
package com.appttude.h_mal.farmr.model
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.utils.calculateDuration
|
||||||
|
import com.appttude.h_mal.farmr.utils.formatToTwoDp
|
||||||
|
|
||||||
data class Shift(
|
data class Shift(
|
||||||
val type: ShiftType,
|
val type: ShiftType,
|
||||||
val description: String,
|
val description: String,
|
||||||
@@ -11,4 +14,52 @@ data class Shift(
|
|||||||
val units: Float?,
|
val units: Float?,
|
||||||
val rateOfPay: Float,
|
val rateOfPay: Float,
|
||||||
val totalPay: Float
|
val totalPay: Float
|
||||||
)
|
) {
|
||||||
|
companion object {
|
||||||
|
// Invocation for Hourly
|
||||||
|
operator fun invoke(
|
||||||
|
description: String,
|
||||||
|
date: String,
|
||||||
|
timeIn: String,
|
||||||
|
timeOut: String,
|
||||||
|
breakMins: Int? = null,
|
||||||
|
rateOfPay: Float
|
||||||
|
): Shift {
|
||||||
|
val breakTime = breakMins ?: 0
|
||||||
|
val duration = calculateDuration(timeIn, timeOut, breakTime)
|
||||||
|
|
||||||
|
return Shift(
|
||||||
|
ShiftType.HOURLY,
|
||||||
|
description,
|
||||||
|
date,
|
||||||
|
timeIn,
|
||||||
|
timeOut,
|
||||||
|
duration,
|
||||||
|
breakTime,
|
||||||
|
0f,
|
||||||
|
rateOfPay,
|
||||||
|
(duration * rateOfPay).formatToTwoDp()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun invoke(
|
||||||
|
description: String,
|
||||||
|
date: String,
|
||||||
|
units: Float,
|
||||||
|
rateOfPay: Float
|
||||||
|
) = Shift(
|
||||||
|
ShiftType.PIECE,
|
||||||
|
description,
|
||||||
|
date,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0f,
|
||||||
|
0,
|
||||||
|
units,
|
||||||
|
rateOfPay,
|
||||||
|
(units * rateOfPay).formatToTwoDp()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
package com.appttude.h_mal.farmr.model
|
package com.appttude.h_mal.farmr.model
|
||||||
|
|
||||||
enum class ShiftType(val type: String) {
|
enum class ShiftType(val type: String){
|
||||||
HOURLY("Hourly"),
|
HOURLY("Hourly"),
|
||||||
PIECE("Piece Rate")
|
PIECE("Piece Rate");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getEnumByType(type: String): ShiftType {
|
||||||
|
return values().first { it.type == type }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
17
app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt
Normal file
17
app/src/main/java/com/appttude/h_mal/farmr/model/Sortable.kt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.appttude.h_mal.farmr.model
|
||||||
|
|
||||||
|
enum class Sortable(val label: String) {
|
||||||
|
ID("Default"),
|
||||||
|
TYPE("Shift Type"),
|
||||||
|
DATE("Date"),
|
||||||
|
DESCRIPTION("Description"),
|
||||||
|
DURATION("Added"), UNITS("Duration"),
|
||||||
|
RATEOFPAY("Rate of pay"),
|
||||||
|
TOTALPAY("Total Pay");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getEnumByType(label: String): Sortable {
|
||||||
|
return Sortable.values().first { it.label == label }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.appttude.h_mal.farmr.model
|
||||||
|
|
||||||
|
data class Success(
|
||||||
|
val successMessage: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.appttude.h_mal.farmr.model
|
||||||
|
|
||||||
|
sealed class ViewState {
|
||||||
|
object HasStarted : ViewState()
|
||||||
|
class HasData<T : Any>(val data: T) : ViewState()
|
||||||
|
class HasError<T : Any>(val error: T) : ViewState()
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.OnClickListener
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.Spinner
|
||||||
|
import androidx.core.widget.doAfterTextChanged
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import com.appttude.h_mal.farmr.base.BaseFragment
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import com.appttude.h_mal.farmr.model.Success
|
||||||
|
import com.appttude.h_mal.farmr.utils.setDatePicker
|
||||||
|
import com.appttude.h_mal.farmr.viewmodel.FilterViewModel
|
||||||
|
|
||||||
|
class FilterDataFragment : BaseFragment<FilterViewModel>(R.layout.fragment_filter_data),
|
||||||
|
AdapterView.OnItemSelectedListener, OnClickListener {
|
||||||
|
private val spinnerList: Array<String> =
|
||||||
|
arrayOf("", ShiftType.HOURLY.type, ShiftType.PIECE.type)
|
||||||
|
|
||||||
|
private lateinit var LocationET: EditText
|
||||||
|
private lateinit var dateFromET: EditText
|
||||||
|
private lateinit var dateToET: EditText
|
||||||
|
private lateinit var typeSpinner: Spinner
|
||||||
|
|
||||||
|
private var descriptionString: String? = null
|
||||||
|
private var dateFromString: String? = null
|
||||||
|
private var dateToString: String? = null
|
||||||
|
private var typeString: String? = null
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setTitle(getString(R.string.title_activity_filter_data))
|
||||||
|
|
||||||
|
LocationET = view.findViewById(R.id.filterLocationEditText)
|
||||||
|
dateFromET = view.findViewById(R.id.fromdateInEditText)
|
||||||
|
dateToET = view.findViewById(R.id.filterDateOutEditText)
|
||||||
|
typeSpinner = view.findViewById(R.id.TypeFilterEditText)
|
||||||
|
val submit: Button = view.findViewById(R.id.submitFiltered)
|
||||||
|
|
||||||
|
val adapter: ArrayAdapter<String> =
|
||||||
|
ArrayAdapter((context)!!, android.R.layout.simple_spinner_dropdown_item, spinnerList)
|
||||||
|
typeSpinner.adapter = adapter
|
||||||
|
|
||||||
|
val filterDetails = viewModel.getFiltrationDetails()
|
||||||
|
|
||||||
|
filterDetails.run {
|
||||||
|
description?.let {
|
||||||
|
LocationET.setText(it)
|
||||||
|
descriptionString = it
|
||||||
|
}
|
||||||
|
dateFrom?.let {
|
||||||
|
dateFromET.setText(it)
|
||||||
|
dateFromString = it
|
||||||
|
}
|
||||||
|
dateTo?.let {
|
||||||
|
dateToET.setText(it)
|
||||||
|
dateToString = it
|
||||||
|
}
|
||||||
|
type?.let {
|
||||||
|
typeString = it
|
||||||
|
val spinnerPosition: Int = adapter.getPosition(it)
|
||||||
|
typeSpinner.setSelection(spinnerPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationET.doAfterTextChanged { descriptionString = it.toString() }
|
||||||
|
dateFromET.setDatePicker { dateFromString = it }
|
||||||
|
dateToET.setDatePicker { dateToString = it }
|
||||||
|
typeSpinner.onItemSelectedListener = this
|
||||||
|
|
||||||
|
submit.setOnClickListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemSelected(
|
||||||
|
parentView: AdapterView<*>?,
|
||||||
|
selectedItemView: View?,
|
||||||
|
position: Int,
|
||||||
|
id: Long
|
||||||
|
) {
|
||||||
|
typeString = when (position) {
|
||||||
|
1 -> ShiftType.HOURLY.type
|
||||||
|
2 -> ShiftType.PIECE.type
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parentView: AdapterView<*>?) {}
|
||||||
|
|
||||||
|
private fun submitFiltrationDetails() {
|
||||||
|
viewModel.applyFilters(descriptionString, dateFromString, dateToString, typeString)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(p0: View?) {
|
||||||
|
submitFiltrationDetails()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(data: Any?) {
|
||||||
|
super.onSuccess(data)
|
||||||
|
if (data is Success) popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
298
app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt
Normal file
298
app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentAddItem.kt
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.RadioButton
|
||||||
|
import android.widget.RadioGroup
|
||||||
|
import android.widget.ScrollView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.widget.doAfterTextChanged
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import com.appttude.h_mal.farmr.base.BackPressedListener
|
||||||
|
import com.appttude.h_mal.farmr.base.BaseFragment
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import com.appttude.h_mal.farmr.model.Success
|
||||||
|
import com.appttude.h_mal.farmr.utils.ID
|
||||||
|
import com.appttude.h_mal.farmr.utils.createDialog
|
||||||
|
import com.appttude.h_mal.farmr.utils.displayToast
|
||||||
|
import com.appttude.h_mal.farmr.utils.formatAsCurrencyString
|
||||||
|
import com.appttude.h_mal.farmr.utils.formatToTwoDpString
|
||||||
|
import com.appttude.h_mal.farmr.utils.hide
|
||||||
|
import com.appttude.h_mal.farmr.utils.popBackStack
|
||||||
|
import com.appttude.h_mal.farmr.utils.setDatePicker
|
||||||
|
import com.appttude.h_mal.farmr.utils.setTimePicker
|
||||||
|
import com.appttude.h_mal.farmr.utils.show
|
||||||
|
import com.appttude.h_mal.farmr.utils.validateField
|
||||||
|
import com.appttude.h_mal.farmr.viewmodel.SubmissionViewModel
|
||||||
|
|
||||||
|
class FragmentAddItem : BaseFragment<SubmissionViewModel>(R.layout.fragment_add_item),
|
||||||
|
RadioGroup.OnCheckedChangeListener, BackPressedListener {
|
||||||
|
|
||||||
|
private lateinit var mHourlyRadioButton: RadioButton
|
||||||
|
private lateinit var mPieceRadioButton: RadioButton
|
||||||
|
private lateinit var mLocationEditText: EditText
|
||||||
|
private lateinit var mDateEditText: EditText
|
||||||
|
private lateinit var mDurationTextView: TextView
|
||||||
|
private lateinit var mTimeInEditText: EditText
|
||||||
|
private lateinit var mTimeOutEditText: EditText
|
||||||
|
private lateinit var mBreakEditText: EditText
|
||||||
|
private lateinit var mUnitEditText: EditText
|
||||||
|
private lateinit var mPayRateEditText: EditText
|
||||||
|
private lateinit var mTotalPayTextView: TextView
|
||||||
|
private lateinit var hourlyDataView: LinearLayout
|
||||||
|
private lateinit var unitsHolder: LinearLayout
|
||||||
|
private lateinit var durationHolder: LinearLayout
|
||||||
|
private lateinit var wholeView: LinearLayout
|
||||||
|
private lateinit var scrollView: ScrollView
|
||||||
|
private lateinit var submitProduct: Button
|
||||||
|
private lateinit var mRadioGroup: RadioGroup
|
||||||
|
|
||||||
|
private var mDate: String? = null
|
||||||
|
private var mDescription: String? = null
|
||||||
|
private var mTimeIn: String? = null
|
||||||
|
private var mTimeOut: String? = null
|
||||||
|
private var mBreaks: Int? = null
|
||||||
|
private var mUnits: Float? = null
|
||||||
|
private var mPayRate = 0f
|
||||||
|
private var mType: ShiftType? = null
|
||||||
|
private var mDuration: Float? = null
|
||||||
|
|
||||||
|
private var id: Long? = null
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
scrollView = view.findViewById(R.id.total_view)
|
||||||
|
mRadioGroup = view.findViewById(R.id.rg)
|
||||||
|
mHourlyRadioButton = view.findViewById(R.id.hourly)
|
||||||
|
mPieceRadioButton = view.findViewById(R.id.piecerate)
|
||||||
|
mLocationEditText = view.findViewById(R.id.locationEditText)
|
||||||
|
mDateEditText = view.findViewById(R.id.dateEditText)
|
||||||
|
mTimeInEditText = view.findViewById(R.id.timeInEditText)
|
||||||
|
mBreakEditText = view.findViewById(R.id.breakEditText)
|
||||||
|
mTimeOutEditText = view.findViewById(R.id.timeOutEditText)
|
||||||
|
mDurationTextView = view.findViewById(R.id.ShiftDuration)
|
||||||
|
mUnitEditText = view.findViewById(R.id.unitET)
|
||||||
|
mPayRateEditText = view.findViewById(R.id.payrateET)
|
||||||
|
mTotalPayTextView = view.findViewById(R.id.totalpayval)
|
||||||
|
hourlyDataView = view.findViewById(R.id.hourly_data_holder)
|
||||||
|
unitsHolder = view.findViewById(R.id.units_holder)
|
||||||
|
durationHolder = view.findViewById(R.id.duration_holder)
|
||||||
|
wholeView = view.findViewById(R.id.whole_view)
|
||||||
|
submitProduct = view.findViewById(R.id.submit)
|
||||||
|
|
||||||
|
mRadioGroup.setOnCheckedChangeListener(this)
|
||||||
|
mLocationEditText.doAfterTextChanged {
|
||||||
|
mDescription = it.toString()
|
||||||
|
}
|
||||||
|
mDateEditText.setDatePicker { mDate = it }
|
||||||
|
mTimeInEditText.setTimePicker {
|
||||||
|
mTimeIn = it
|
||||||
|
calculateTotalPay()
|
||||||
|
}
|
||||||
|
mTimeOutEditText.setTimePicker {
|
||||||
|
mTimeOut = it
|
||||||
|
calculateTotalPay()
|
||||||
|
}
|
||||||
|
mBreakEditText.doAfterTextChanged {
|
||||||
|
mBreaks = it.toString().toIntOrNull() ?: 0
|
||||||
|
calculateTotalPay()
|
||||||
|
}
|
||||||
|
mUnitEditText.doAfterTextChanged {
|
||||||
|
it.toString().toFloatOrNull()?.let { u -> mUnits = u }
|
||||||
|
calculateTotalPay()
|
||||||
|
}
|
||||||
|
mPayRateEditText.doAfterTextChanged {
|
||||||
|
it.toString().toFloatOrNull()?.let { p ->
|
||||||
|
mPayRate = p
|
||||||
|
calculateTotalPay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submitProduct.setOnClickListener { submitShift() }
|
||||||
|
|
||||||
|
setupViewAfterViewCreated()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupViewAfterViewCreated() {
|
||||||
|
id = arguments?.getLong(ID)
|
||||||
|
wholeView.hide()
|
||||||
|
|
||||||
|
val title = when (arguments?.containsKey(ID)) {
|
||||||
|
true -> {
|
||||||
|
// Since we are editing a shift lets load the shift data into the views
|
||||||
|
viewModel.getCurrentShift(arguments!!.getLong(ID))?.run {
|
||||||
|
mLocationEditText.setText(description)
|
||||||
|
mDateEditText.setText(date)
|
||||||
|
|
||||||
|
// Set types
|
||||||
|
mType = ShiftType.getEnumByType(type)
|
||||||
|
mDescription = description
|
||||||
|
mDate = date
|
||||||
|
mPayRate = rateOfPay
|
||||||
|
|
||||||
|
when (ShiftType.getEnumByType(type)) {
|
||||||
|
ShiftType.HOURLY -> {
|
||||||
|
mHourlyRadioButton.isChecked = true
|
||||||
|
mPieceRadioButton.isChecked = false
|
||||||
|
mTimeInEditText.setText(timeIn)
|
||||||
|
mTimeOutEditText.setText(timeOut)
|
||||||
|
mBreakEditText.setText(breakMins.toString())
|
||||||
|
val durationText = "${duration.formatToTwoDpString()} Hours"
|
||||||
|
mDurationTextView.text = durationText
|
||||||
|
|
||||||
|
// Set fields
|
||||||
|
mTimeIn = timeIn
|
||||||
|
mTimeOut = timeOut
|
||||||
|
mBreaks = breakMins
|
||||||
|
}
|
||||||
|
|
||||||
|
ShiftType.PIECE -> {
|
||||||
|
mHourlyRadioButton.isChecked = false
|
||||||
|
mPieceRadioButton.isChecked = true
|
||||||
|
mUnitEditText.setText(units.formatToTwoDpString())
|
||||||
|
|
||||||
|
// Set piece rate units
|
||||||
|
mUnits = units
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mPayRateEditText.setText(rateOfPay.formatAsCurrencyString())
|
||||||
|
mTotalPayTextView.text = totalPay.formatAsCurrencyString()
|
||||||
|
|
||||||
|
calculateTotalPay()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return title
|
||||||
|
getString(R.string.edit_item_title)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> getString(R.string.add_item_title)
|
||||||
|
}
|
||||||
|
setTitle(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCheckedChanged(radioGroup: RadioGroup, id: Int) {
|
||||||
|
when (radioGroup.checkedRadioButtonId) {
|
||||||
|
R.id.hourly -> {
|
||||||
|
mType = ShiftType.HOURLY
|
||||||
|
wholeView.show()
|
||||||
|
unitsHolder.hide()
|
||||||
|
hourlyDataView.show()
|
||||||
|
durationHolder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.piecerate -> {
|
||||||
|
mType = ShiftType.PIECE
|
||||||
|
wholeView.show()
|
||||||
|
unitsHolder.show()
|
||||||
|
hourlyDataView.hide()
|
||||||
|
durationHolder.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun submitShift() {
|
||||||
|
mDate.validateField({ !it.isNullOrBlank() }) {
|
||||||
|
onFailure("Date field cannot be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mDescription.validateField({ !it.isNullOrBlank() }) {
|
||||||
|
onFailure("Description field cannot be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mPayRate.validateField({ !it.isNaN() }) {
|
||||||
|
onFailure("Rate of pay field cannot be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPieceRadioButton.isChecked) {
|
||||||
|
|
||||||
|
mUnits.validateField({ it != null && it >= 0 }) {
|
||||||
|
onFailure("Units field cannot be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (id != null) {
|
||||||
|
// update
|
||||||
|
viewModel.updateShift(
|
||||||
|
id!!,
|
||||||
|
description = mDescription,
|
||||||
|
date = mDate,
|
||||||
|
units = mUnits,
|
||||||
|
rateOfPay = mPayRate
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// insert
|
||||||
|
viewModel.insertPieceRateShift(mDescription!!, mDate!!, mUnits!!, mPayRate)
|
||||||
|
}
|
||||||
|
} else if (mHourlyRadioButton.isChecked) {
|
||||||
|
if (id != null) {
|
||||||
|
// update
|
||||||
|
viewModel.updateShift(
|
||||||
|
id!!,
|
||||||
|
description = mDescription,
|
||||||
|
date = mDate,
|
||||||
|
rateOfPay = mPayRate,
|
||||||
|
timeIn = mTimeIn,
|
||||||
|
timeOut = mTimeOut,
|
||||||
|
breakMins = mBreaks
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// insert
|
||||||
|
viewModel.insertHourlyShift(
|
||||||
|
mDescription!!,
|
||||||
|
mDate!!,
|
||||||
|
mPayRate,
|
||||||
|
mTimeIn,
|
||||||
|
mTimeOut,
|
||||||
|
mBreaks
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateTotalPay() {
|
||||||
|
mType?.let {
|
||||||
|
val total = when (it) {
|
||||||
|
ShiftType.HOURLY -> {
|
||||||
|
// Calculate duration before total pay calculation
|
||||||
|
mDuration = viewModel.retrieveDurationText(mTimeIn, mTimeOut, mBreaks) ?: return
|
||||||
|
mDurationTextView.text =
|
||||||
|
StringBuilder().append(mDuration).append(" hours").toString()
|
||||||
|
mDuration!! * mPayRate
|
||||||
|
}
|
||||||
|
ShiftType.PIECE -> {
|
||||||
|
(mUnits ?: 0f) * mPayRate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mTotalPayTextView.text = total.formatAsCurrencyString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(): Boolean {
|
||||||
|
if (mRadioGroup.checkedRadioButtonId == -1) {
|
||||||
|
mActivity?.popBackStack()
|
||||||
|
} else {
|
||||||
|
requireContext().createDialog(
|
||||||
|
title = "Discard Changes?",
|
||||||
|
message = "Are you sure you want to discard changes?",
|
||||||
|
displayCancel = true,
|
||||||
|
okCallback = { _, _ ->
|
||||||
|
mActivity?.popBackStack()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(data: Any?) {
|
||||||
|
super.onSuccess(data)
|
||||||
|
if (data is Success) {
|
||||||
|
displayToast(data.successMessage)
|
||||||
|
popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
260
app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt
Normal file
260
app/src/main/java/com/appttude/h_mal/farmr/ui/FragmentMain.kt
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import com.appttude.h_mal.farmr.base.BackPressedListener
|
||||||
|
import com.appttude.h_mal.farmr.base.BaseFragment
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
|
||||||
|
import com.appttude.h_mal.farmr.model.Order
|
||||||
|
import com.appttude.h_mal.farmr.model.Sortable
|
||||||
|
import com.appttude.h_mal.farmr.model.Success
|
||||||
|
import com.appttude.h_mal.farmr.utils.createDialog
|
||||||
|
import com.appttude.h_mal.farmr.utils.displayToast
|
||||||
|
import com.appttude.h_mal.farmr.utils.hide
|
||||||
|
import com.appttude.h_mal.farmr.utils.navigateToFragment
|
||||||
|
import com.appttude.h_mal.farmr.utils.show
|
||||||
|
import com.appttude.h_mal.farmr.viewmodel.MainViewModel
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
|
class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPressedListener {
|
||||||
|
private lateinit var productListView: RecyclerView
|
||||||
|
private lateinit var emptyView: View
|
||||||
|
private lateinit var mAdapter: ShiftListAdapter
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setTitle("Shift List")
|
||||||
|
// Inflate the layout for this fragment
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
mAdapter = ShiftListAdapter(this) {
|
||||||
|
viewModel.deleteShift(it)
|
||||||
|
}
|
||||||
|
productListView = view.findViewById(R.id.list_item_view)
|
||||||
|
productListView.adapter = mAdapter
|
||||||
|
emptyView = view.findViewById(R.id.empty_view)
|
||||||
|
|
||||||
|
mAdapter.registerAdapterDataObserver(object : AdapterDataObserver() {
|
||||||
|
override fun onChanged() {
|
||||||
|
super.onChanged()
|
||||||
|
if (mAdapter.itemCount == 0) emptyView.show()
|
||||||
|
else emptyView.hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
view.findViewById<FloatingActionButton>(R.id.fab1).setOnClickListener {
|
||||||
|
navigateToFragment(FragmentAddItem(), name = "additem")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
viewModel.refreshLiveData()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(data: Any?) {
|
||||||
|
super.onSuccess(data)
|
||||||
|
if (data is List<*>) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
mAdapter.submitList(data as List<ShiftObject>)
|
||||||
|
}
|
||||||
|
if (data is Success) {
|
||||||
|
displayToast(data.successMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.delete_all -> {
|
||||||
|
deleteAllProducts()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.help -> {
|
||||||
|
AlertDialog.Builder(context)
|
||||||
|
.setTitle("Help & Support:")
|
||||||
|
.setView(R.layout.dialog_layout)
|
||||||
|
.setPositiveButton(android.R.string.ok) { arg0, _ -> arg0.dismiss() }
|
||||||
|
.create().show()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.filter_data -> {
|
||||||
|
navigateToFragment(FilterDataFragment(), name = "filterdata")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.sort_data -> {
|
||||||
|
sortData()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.clear_filter -> {
|
||||||
|
viewModel.clearFilters()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.export_data -> {
|
||||||
|
if (checkStoragePermissions(activity)) {
|
||||||
|
AlertDialog.Builder(context)
|
||||||
|
.setTitle("Export?")
|
||||||
|
.setMessage("Exporting current filtered data. Continue?")
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ -> exportData() }
|
||||||
|
.create().show()
|
||||||
|
} else {
|
||||||
|
displayToast("Storage permissions required")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.action_favorite -> {
|
||||||
|
AlertDialog.Builder(context)
|
||||||
|
.setTitle("Info:")
|
||||||
|
.setMessage(viewModel.getInformation())
|
||||||
|
.setPositiveButton(android.R.string.ok) { arg0, _ ->
|
||||||
|
arg0.dismiss()
|
||||||
|
}.create().show()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sortData() {
|
||||||
|
val groupName = Sortable.values().map { it.label }.toTypedArray()
|
||||||
|
var sort = Sortable.ID
|
||||||
|
|
||||||
|
val sortAndOrder = viewModel.getSortAndOrder()
|
||||||
|
val checkedItem = Sortable.values().indexOf(sortAndOrder.first)
|
||||||
|
|
||||||
|
AlertDialog.Builder(context)
|
||||||
|
.setTitle("Sort by:")
|
||||||
|
.setSingleChoiceItems(
|
||||||
|
groupName,
|
||||||
|
checkedItem
|
||||||
|
) { _, p1 -> sort = Sortable.getEnumByType(groupName[p1]) }
|
||||||
|
.setPositiveButton("Ascending") { dialog, _ ->
|
||||||
|
viewModel.setSortAndOrder(sort)
|
||||||
|
dialog.dismiss()
|
||||||
|
}.setNegativeButton("Descending") { dialog, _ ->
|
||||||
|
viewModel.setSortAndOrder(sort, Order.DESCENDING)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.create().show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteAllProducts() {
|
||||||
|
requireContext().createDialog(
|
||||||
|
"Warning",
|
||||||
|
message = "Are you sure you want to delete all date?",
|
||||||
|
displayCancel = true,
|
||||||
|
okCallback = { _, _ ->
|
||||||
|
viewModel.deleteAllShifts()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportData() {
|
||||||
|
val permission =
|
||||||
|
ActivityCompat.checkSelfPermission(requireActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Toast.makeText(context, "Storage permissions not granted", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileName = "shifthistory.xls"
|
||||||
|
val file = File(requireContext().externalCacheDir, fileName)
|
||||||
|
|
||||||
|
viewModel.createExcelSheet(file)?.let {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
val excelUri = FileProvider.getUriForFile(
|
||||||
|
requireContext(),
|
||||||
|
requireContext().applicationContext.packageName + ".provider",
|
||||||
|
file
|
||||||
|
)
|
||||||
|
intent.setDataAndType(excelUri, "application/vnd.ms-excel")
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
println("request code$requestCode")
|
||||||
|
if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
|
||||||
|
if (grantResults.isNotEmpty()
|
||||||
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
exportDialog()
|
||||||
|
} else {
|
||||||
|
displayToast("Storage Permissions denied")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportDialog() {
|
||||||
|
AlertDialog.Builder(context)
|
||||||
|
.setTitle("Export?")
|
||||||
|
.setMessage("Exporting current filtered data. Continue?")
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ -> exportData() }.create().show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkStoragePermissions(activity: Activity?): Boolean {
|
||||||
|
var status = false
|
||||||
|
val permission = ActivityCompat.checkSelfPermission(
|
||||||
|
activity!!,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
if (permission == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
status = true
|
||||||
|
}
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(): Boolean {
|
||||||
|
requireContext().createDialog(
|
||||||
|
title = "Leave?",
|
||||||
|
message = "Are you sure you want to exit Farmr?",
|
||||||
|
displayCancel = true,
|
||||||
|
okCallback = { _, _ ->
|
||||||
|
val intent = Intent(Intent.ACTION_MAIN)
|
||||||
|
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
intent.addCategory(Intent.CATEGORY_HOME)
|
||||||
|
startActivity(intent)
|
||||||
|
requireActivity().finish()
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import com.appttude.h_mal.farmr.base.BaseFragment
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import com.appttude.h_mal.farmr.utils.CURRENCY
|
||||||
|
import com.appttude.h_mal.farmr.utils.formatAsCurrencyString
|
||||||
|
import com.appttude.h_mal.farmr.utils.hide
|
||||||
|
import com.appttude.h_mal.farmr.utils.navigateToFragment
|
||||||
|
import com.appttude.h_mal.farmr.utils.show
|
||||||
|
import com.appttude.h_mal.farmr.viewmodel.InfoViewModel
|
||||||
|
|
||||||
|
class FurtherInfoFragment : BaseFragment<InfoViewModel>(R.layout.fragment_futher_info) {
|
||||||
|
private lateinit var typeTV: TextView
|
||||||
|
private lateinit var descriptionTV: TextView
|
||||||
|
private lateinit var dateTV: TextView
|
||||||
|
private lateinit var times: TextView
|
||||||
|
private lateinit var breakTV: TextView
|
||||||
|
private lateinit var durationTV: TextView
|
||||||
|
private lateinit var unitsTV: TextView
|
||||||
|
private lateinit var payRateTV: TextView
|
||||||
|
private lateinit var totalPayTV: TextView
|
||||||
|
private lateinit var hourlyDetailHolder: LinearLayout
|
||||||
|
private lateinit var unitsHolder: LinearLayout
|
||||||
|
private lateinit var wholeView: LinearLayout
|
||||||
|
private lateinit var progressBarFI: ProgressBar
|
||||||
|
private lateinit var editButton: Button
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setTitle(getString(R.string.further_info_title))
|
||||||
|
|
||||||
|
progressBarFI = view.findViewById(R.id.progressBar_info)
|
||||||
|
wholeView = view.findViewById(R.id.further_info_view)
|
||||||
|
typeTV = view.findViewById(R.id.details_shift)
|
||||||
|
descriptionTV = view.findViewById(R.id.details_desc)
|
||||||
|
dateTV = view.findViewById(R.id.details_date)
|
||||||
|
times = view.findViewById(R.id.details_time)
|
||||||
|
breakTV = view.findViewById(R.id.details_breaks)
|
||||||
|
durationTV = view.findViewById(R.id.details_duration)
|
||||||
|
unitsTV = view.findViewById(R.id.details_units)
|
||||||
|
payRateTV = view.findViewById(R.id.details_pay_rate)
|
||||||
|
totalPayTV = view.findViewById(R.id.details_totalpay)
|
||||||
|
editButton = view.findViewById(R.id.details_edit)
|
||||||
|
hourlyDetailHolder = view.findViewById(R.id.details_hourly_details)
|
||||||
|
unitsHolder = view.findViewById(R.id.details_units_holder)
|
||||||
|
|
||||||
|
editButton.setOnClickListener {
|
||||||
|
navigateToFragment(FragmentAddItem(), name = "additem", bundle = arguments!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.retrieveData(arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(data: Any?) {
|
||||||
|
super.onSuccess(data)
|
||||||
|
if (data is ShiftObject) data.setupView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ShiftObject.setupView() {
|
||||||
|
typeTV.text = type
|
||||||
|
descriptionTV.text = description
|
||||||
|
dateTV.text = date
|
||||||
|
payRateTV.text = rateOfPay.toString()
|
||||||
|
totalPayTV.text = StringBuilder(CURRENCY).append(totalPay).toString()
|
||||||
|
|
||||||
|
when (ShiftType.getEnumByType(type)) {
|
||||||
|
ShiftType.HOURLY -> {
|
||||||
|
hourlyDetailHolder.show()
|
||||||
|
unitsHolder.hide()
|
||||||
|
times.text = StringBuilder(timeIn).append("-").append(timeOut).toString()
|
||||||
|
breakTV.text = StringBuilder().append(breakMins).append(" mins").toString()
|
||||||
|
durationTV.text = viewModel.buildDurationSummary(this)
|
||||||
|
val paymentSummary =
|
||||||
|
StringBuilder().append(duration).append(" Hours @ ")
|
||||||
|
.append(rateOfPay.formatAsCurrencyString()).append(" per Hour").append("\n")
|
||||||
|
.append("Equals: ").append(totalPay.formatAsCurrencyString())
|
||||||
|
totalPayTV.text = paymentSummary
|
||||||
|
}
|
||||||
|
|
||||||
|
ShiftType.PIECE -> {
|
||||||
|
hourlyDetailHolder.hide()
|
||||||
|
unitsHolder.show()
|
||||||
|
unitsTV.text = units.toString()
|
||||||
|
|
||||||
|
val paymentSummary =
|
||||||
|
StringBuilder().append(units.formatAsCurrencyString()).append(" Units @ ")
|
||||||
|
.append(rateOfPay.formatAsCurrencyString()).append(" per Unit").append("\n")
|
||||||
|
.append("Equals: ").append(totalPay.formatAsCurrencyString())
|
||||||
|
totalPayTV.text = paymentSummary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import com.appttude.h_mal.farmr.base.BackPressedListener
|
||||||
|
import com.appttude.h_mal.farmr.base.BaseActivity
|
||||||
|
import com.appttude.h_mal.farmr.utils.popBackStack
|
||||||
|
|
||||||
|
class MainActivity : BaseActivity() {
|
||||||
|
private lateinit var toolbar: Toolbar
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.main_view)
|
||||||
|
toolbar = findViewById(R.id.toolbar)
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
|
verifyStoragePermissions(this)
|
||||||
|
|
||||||
|
val fragmentTransaction = supportFragmentManager.beginTransaction()
|
||||||
|
fragmentTransaction.replace(R.id.container, FragmentMain()).addToBackStack("main").commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
|
menuInflater.inflate(R.menu.menu_main, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
val currentFragment = supportFragmentManager.findFragmentById(R.id.container)
|
||||||
|
if (currentFragment is BackPressedListener) {
|
||||||
|
currentFragment.onBackPressed()
|
||||||
|
} else {
|
||||||
|
if (supportFragmentManager.backStackEntryCount > 1) {
|
||||||
|
popBackStack()
|
||||||
|
} else {
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage Permissions
|
||||||
|
private val REQUEST_EXTERNAL_STORAGE = 1
|
||||||
|
private val PERMISSIONS_STORAGE = arrayOf(
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the app has permission to write to device storage
|
||||||
|
*
|
||||||
|
* If the app does not has permission then the user will be prompted to grant permissions
|
||||||
|
*
|
||||||
|
* @param activity
|
||||||
|
*/
|
||||||
|
fun verifyStoragePermissions(activity: Activity?) {
|
||||||
|
// Check if we have write permission
|
||||||
|
val permission = ActivityCompat.checkSelfPermission(
|
||||||
|
activity!!,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// We don't have permission so prompt the user
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
activity,
|
||||||
|
PERMISSIONS_STORAGE,
|
||||||
|
REQUEST_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package com.appttude.h_mal.farmr.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import com.appttude.h_mal.farmr.base.BaseRecyclerAdapter
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import com.appttude.h_mal.farmr.utils.ID
|
||||||
|
import com.appttude.h_mal.farmr.utils.generateView
|
||||||
|
import com.appttude.h_mal.farmr.utils.navigateToFragment
|
||||||
|
|
||||||
|
class ShiftListAdapter(
|
||||||
|
private val fragment: Fragment,
|
||||||
|
private val longPressCallback: (Long) -> Unit
|
||||||
|
) : ListAdapter<ShiftObject, BaseRecyclerAdapter.CurrentViewHolder>(diffCallBack) {
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int
|
||||||
|
): BaseRecyclerAdapter.CurrentViewHolder {
|
||||||
|
val currentViewHolder = parent.generateView(R.layout.list_item_1)
|
||||||
|
return BaseRecyclerAdapter.CurrentViewHolder(currentViewHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onBindViewHolder(holder: BaseRecyclerAdapter.CurrentViewHolder, position: Int) {
|
||||||
|
val view = holder.itemView
|
||||||
|
val data = getItem(position)
|
||||||
|
|
||||||
|
val descriptionTextView: TextView = view.findViewById(R.id.location)
|
||||||
|
val dateTextView: TextView = view.findViewById(R.id.date)
|
||||||
|
val totalPay: TextView = view.findViewById(R.id.total_pay)
|
||||||
|
val hoursView: TextView = view.findViewById(R.id.hours)
|
||||||
|
val h: TextView = view.findViewById(R.id.h)
|
||||||
|
val minutesView: TextView = view.findViewById(R.id.minutes)
|
||||||
|
val m: TextView = view.findViewById(R.id.m)
|
||||||
|
val editView: ImageView = view.findViewById(R.id.imageView)
|
||||||
|
h.text = "h"
|
||||||
|
m.text = "m"
|
||||||
|
val typeText: String = data.type
|
||||||
|
val descriptionText: String = data.description
|
||||||
|
val dateText: String = data.date
|
||||||
|
val totalPayText: String = data.totalPay.toString()
|
||||||
|
|
||||||
|
descriptionTextView.text = descriptionText
|
||||||
|
dateTextView.text = dateText
|
||||||
|
totalPay.text = totalPayText
|
||||||
|
|
||||||
|
when (ShiftType.getEnumByType(typeText)) {
|
||||||
|
ShiftType.HOURLY -> {
|
||||||
|
val time = data.getHoursMinutesPairFromDuration()
|
||||||
|
|
||||||
|
hoursView.text = time.first
|
||||||
|
minutesView.text = time.second
|
||||||
|
}
|
||||||
|
|
||||||
|
ShiftType.PIECE -> {
|
||||||
|
val unitsText: String = data.units.toString()
|
||||||
|
|
||||||
|
hoursView.text = unitsText
|
||||||
|
h.text = ""
|
||||||
|
minutesView.text = ""
|
||||||
|
m.text = "pcs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val b: Bundle = Bundle()
|
||||||
|
b.putLong(ID, data.id)
|
||||||
|
view.setOnClickListener {
|
||||||
|
// Navigate to further info
|
||||||
|
fragment.navigateToFragment(
|
||||||
|
FurtherInfoFragment(),
|
||||||
|
bundle = b,
|
||||||
|
name = "furtherinfo"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
editView.setOnClickListener {
|
||||||
|
// Navigate to edit
|
||||||
|
fragment.navigateToFragment(
|
||||||
|
FragmentAddItem(),
|
||||||
|
bundle = b,
|
||||||
|
name = "additem"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
view.setOnLongClickListener {
|
||||||
|
AlertDialog.Builder(it.context)
|
||||||
|
.setMessage("Are you sure you want to delete")
|
||||||
|
.setPositiveButton("delete") { _, _ -> longPressCallback.invoke(data.id) }
|
||||||
|
.setNegativeButton("cancel") { dialog, _ ->
|
||||||
|
dialog?.dismiss()
|
||||||
|
}
|
||||||
|
.create().show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val diffCallBack = object : DiffUtil.ItemCallback<ShiftObject>() {
|
||||||
|
override fun areItemsTheSame(oldItem: ShiftObject, newItem: ShiftObject): Boolean {
|
||||||
|
return oldItem.id == newItem.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: ShiftObject, newItem: ShiftObject): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +1,32 @@
|
|||||||
package com.appttude.h_mal.farmr
|
package com.appttude.h_mal.farmr.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.view.View
|
import android.os.Looper
|
||||||
import android.widget.RelativeLayout
|
import com.appttude.h_mal.farmr.R
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by h_mal on 27/06/2017.
|
* Created by h_mal on 27/06/2017.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("CustomSplashScreen")
|
||||||
class SplashScreen : Activity() {
|
class SplashScreen : Activity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_splash)
|
setContentView(R.layout.activity_splash)
|
||||||
val bundle = ActivityOptionsCompat.makeCustomAnimation(this, R.anim.hyperspace_jump, android.R.anim.fade_out).toBundle()
|
|
||||||
val relativeLayout = findViewById<View>(R.id.splash_layout) as RelativeLayout
|
|
||||||
val i = Intent(this@SplashScreen, MainActivity::class.java)
|
val i = Intent(this@SplashScreen, MainActivity::class.java)
|
||||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
Handler().postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
// This method will be executed once the timer is over
|
|
||||||
// Start your app main activity
|
|
||||||
// startActivity(i,bundle);
|
|
||||||
startActivity(i)
|
startActivity(i)
|
||||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
// finish();
|
}, SPLASH_TIME_OUT)
|
||||||
}, SPLASH_TIME_OUT.toLong())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Splash screen timer
|
// Splash screen timer
|
||||||
private const val SPLASH_TIME_OUT = 2000
|
const val SPLASH_TIME_OUT: Long = 2000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.appttude.h_mal.farmr.utils
|
||||||
|
|
||||||
|
const val LEGACY = "LEGACY_"
|
||||||
|
const val DATE_FORMAT = "yyyy-MM-dd"
|
||||||
|
const val TIME_FORMAT = "hh:mm"
|
||||||
|
const val ID = "ID"
|
||||||
|
const val CURRENCY = "£"
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.appttude.h_mal.farmr.utils
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.text.NumberFormat
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Currency
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
fun String.formatToTwoDp(): Float {
|
||||||
|
val formattedString = String.format("%.2f", this)
|
||||||
|
return formattedString.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Float.formatToTwoDp(): Float {
|
||||||
|
val formattedString = String.format("%.2f", this)
|
||||||
|
return formattedString.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Float.formatAsCurrencyString(): String? {
|
||||||
|
val format: NumberFormat = NumberFormat.getCurrencyInstance()
|
||||||
|
format.maximumFractionDigits = 2
|
||||||
|
format.currency = Currency.getInstance("GBP")
|
||||||
|
|
||||||
|
return format.format(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Float.formatToTwoDpString(): String {
|
||||||
|
return formatToTwoDp().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.dateStringIsValid(): Boolean {
|
||||||
|
return "([0-9]{4})-([0-9]{2})-([0-9]{2})".toPattern().matcher(this).matches()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.timeStringIsValid(): Boolean {
|
||||||
|
return "^([0-1]?[0-9]|2[0-3]):[0-5][0-9]\$".toPattern().matcher(this).matches()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Calendar.getTimeString(): String {
|
||||||
|
val format = SimpleDateFormat(TIME_FORMAT, Locale.getDefault())
|
||||||
|
return format.format(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.convertDateString(format: String = DATE_FORMAT): Date? {
|
||||||
|
val formatter = SimpleDateFormat(format, Locale.getDefault())
|
||||||
|
return formatter.parse(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* turns "HH:mm" into an hour and minutes pair
|
||||||
|
*
|
||||||
|
* eg:
|
||||||
|
* @param 13:45
|
||||||
|
* @return Pair(13, 45)
|
||||||
|
*/
|
||||||
|
fun convertTimeStringToHourMinutesPair(timeString: String): Pair<Int, Int> {
|
||||||
|
val split = timeString.split(":")
|
||||||
|
if (split.size != 2) throw ArrayIndexOutOfBoundsException()
|
||||||
|
return Pair(split.first().toInt(), split[1].toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* calculate the duration between two 24 hour time strings minus the break in minutes
|
||||||
|
*
|
||||||
|
* can also calculate when time to string in past midnight eg: 23:00, 04:45, 30
|
||||||
|
* @return 5.75
|
||||||
|
*/
|
||||||
|
fun calculateDuration(timeIn: String, timeOut: String, breaks: Int): Float {
|
||||||
|
val timeFrom = convertTimeStringToHourMinutesPair(timeIn)
|
||||||
|
val timeTo = convertTimeStringToHourMinutesPair(timeOut)
|
||||||
|
|
||||||
|
val hoursIn = timeFrom.first
|
||||||
|
val minutesIn = timeFrom.second
|
||||||
|
val hoursOut = timeTo.first
|
||||||
|
val minutesOut = timeTo.second
|
||||||
|
|
||||||
|
var duration: Float = if (hoursOut > hoursIn) {
|
||||||
|
((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60)))
|
||||||
|
} else {
|
||||||
|
(((hoursOut.toFloat() + (minutesOut.toFloat() / 60)) - (hoursIn.toFloat() + (minutesIn.toFloat() / 60))) + 24)
|
||||||
|
}
|
||||||
|
if ((breaks.toFloat() / 60) > duration) throw IOException("Breaks duration cannot be larger than shift duration")
|
||||||
|
duration -= (breaks.toFloat() / 60)
|
||||||
|
|
||||||
|
return duration.formatToTwoDp()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateDuration(timeIn: String?, timeOut: String?, breaks: Int?): Float {
|
||||||
|
val calendar by lazy { Calendar.getInstance() }
|
||||||
|
val insertTimeIn = timeIn ?: calendar.getTimeString()
|
||||||
|
val insertTimeOut = timeOut ?: calendar.getTimeString()
|
||||||
|
|
||||||
|
return calculateDuration(insertTimeIn, insertTimeOut, breaks ?: 0)
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.appttude.h_mal.farmr.utils
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.model.Order
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <CLASS : Any> Any.getGenericClassAt(position: Int): KClass<CLASS> =
|
||||||
|
((javaClass.genericSuperclass as? ParameterizedType)
|
||||||
|
?.actualTypeArguments?.getOrNull(position) as? Class<CLASS>)
|
||||||
|
?.kotlin
|
||||||
|
?: throw IllegalStateException("Can not find class from generic argument")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param validate when result is false then we trigger
|
||||||
|
* @param onError
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @sample
|
||||||
|
* var s: String?
|
||||||
|
* i.validate{!i.isNullOrEmpty()} { print("string is empty") }
|
||||||
|
*/
|
||||||
|
inline fun<T: Any?> T.validateField(validate: (T) -> Boolean, onError: () -> Unit) {
|
||||||
|
if (!validate.invoke(this)) {
|
||||||
|
onError.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all elements sorted according to the specified comparator. In order of ascending or descending
|
||||||
|
* The sort is stable. It means that equal elements preserve their order relative to each other after sorting.
|
||||||
|
*/
|
||||||
|
inline fun <T, R : Comparable<R>> Iterable<T>.sortedByOrder(order: Order = Order.ASCENDING, crossinline selector: (T) -> R?): List<T> {
|
||||||
|
return when (order) {
|
||||||
|
Order.ASCENDING -> sortedWith(compareBy(selector))
|
||||||
|
Order.DESCENDING -> sortedWith(compareByDescending(selector))
|
||||||
|
}
|
||||||
|
}
|
||||||
177
app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt
Normal file
177
app/src/main/java/com/appttude/h_mal/farmr/utils/ViewUtils.kt
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package com.appttude.h_mal.farmr.utils
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.app.DatePickerDialog
|
||||||
|
import android.app.TimePickerDialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnimationUtils
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.AnimRes
|
||||||
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.appttude.h_mal.farmr.R
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
fun View.show() {
|
||||||
|
this.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.hide() {
|
||||||
|
this.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.displayToast(message: String) {
|
||||||
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.displayToast(message: String) {
|
||||||
|
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ViewGroup.generateView(layoutId: Int): View = LayoutInflater
|
||||||
|
.from(context)
|
||||||
|
.inflate(layoutId, this, false)
|
||||||
|
|
||||||
|
fun Fragment.hideKeyboard() {
|
||||||
|
val imm = context?.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?
|
||||||
|
imm?.hideSoftInputFromWindow(view?.windowToken, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.triggerAnimation(@AnimRes id: Int, complete: (View) -> Unit) {
|
||||||
|
val animation = AnimationUtils.loadAnimation(context, id)
|
||||||
|
animation.setAnimationListener(object : Animation.AnimationListener {
|
||||||
|
override fun onAnimationEnd(animation: Animation?) = complete(this@triggerAnimation)
|
||||||
|
override fun onAnimationStart(a: Animation?) {}
|
||||||
|
override fun onAnimationRepeat(a: Animation?) {}
|
||||||
|
})
|
||||||
|
startAnimation(animation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.navigateToFragment(fragment: Fragment, @IdRes container: Int = R.id.container, name: String = "") {
|
||||||
|
val fragmentTransaction = requireActivity().supportFragmentManager.beginTransaction()
|
||||||
|
fragmentTransaction.replace(container, fragment).addToBackStack(name).commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.navigateToFragment(fragment: Fragment, @IdRes container: Int = R.id.container, name: String = "", bundle: Bundle) {
|
||||||
|
val fragmentTransaction = requireActivity().supportFragmentManager.beginTransaction()
|
||||||
|
fragmentTransaction.replace(container, fragment.apply { arguments = bundle }).addToBackStack(name).commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.createDialog(
|
||||||
|
title: String?,
|
||||||
|
message: String?,
|
||||||
|
displayCancel: Boolean = false,
|
||||||
|
displayOk: Boolean = true,
|
||||||
|
cancelCallback: DialogInterface.OnClickListener? = null,
|
||||||
|
okCallback: DialogInterface.OnClickListener? = null,
|
||||||
|
) {
|
||||||
|
val builder = AlertDialog.Builder(this)
|
||||||
|
title?.let { builder.setTitle(it) }
|
||||||
|
message?.let { builder.setMessage(it) }
|
||||||
|
if (displayCancel) {
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, cancelCallback)
|
||||||
|
}
|
||||||
|
if (displayOk) {
|
||||||
|
builder.setPositiveButton(android.R.string.ok, okCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.create().show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AppCompatActivity.popBackStack() {
|
||||||
|
supportFragmentManager.popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun EditText.setTimePicker(onSelected: (String) -> Unit) {
|
||||||
|
var mHoursOut: Int
|
||||||
|
var mMinutesOut: Int
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
val mCurrentTime by lazy { Calendar.getInstance() }
|
||||||
|
if (!text.isNullOrEmpty()) {
|
||||||
|
// EditText contains text - lets try set the parse the text
|
||||||
|
try {
|
||||||
|
val convertedString = convertTimeStringToHourMinutesPair(text.toString())
|
||||||
|
mHoursOut = convertedString.first
|
||||||
|
mMinutesOut = convertedString.second
|
||||||
|
} catch (e: Exception) {
|
||||||
|
mHoursOut = mCurrentTime[Calendar.HOUR_OF_DAY]
|
||||||
|
mMinutesOut = mCurrentTime[Calendar.MINUTE]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mHoursOut = mCurrentTime[Calendar.HOUR_OF_DAY]
|
||||||
|
mMinutesOut = mCurrentTime[Calendar.MINUTE]
|
||||||
|
}
|
||||||
|
val mTimePicker = TimePickerDialog(this.context,
|
||||||
|
{ _, selectedHour, selectedMinute ->
|
||||||
|
val ddTime = String.format("%02d", selectedHour) + ":" + String.format(
|
||||||
|
"%02d",
|
||||||
|
selectedMinute
|
||||||
|
)
|
||||||
|
setText(ddTime)
|
||||||
|
onSelected.invoke(ddTime)
|
||||||
|
}, mHoursOut, mMinutesOut, true
|
||||||
|
) //Yes 24 hour time
|
||||||
|
mTimePicker.setTitle("Select Time")
|
||||||
|
mTimePicker.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun EditText.setDatePicker(onSelected: (String) -> Unit) {
|
||||||
|
//To show current date in the datepicker
|
||||||
|
var mYear: Int
|
||||||
|
var mMonth: Int
|
||||||
|
var mDay: Int
|
||||||
|
|
||||||
|
val mCurrentDate by lazy { Calendar.getInstance() }
|
||||||
|
|
||||||
|
if (!text.isNullOrEmpty()) {
|
||||||
|
try {
|
||||||
|
val dateSplit = text.split("-")
|
||||||
|
|
||||||
|
mYear = dateSplit[0].toInt()
|
||||||
|
mMonth = dateSplit[1].toInt()
|
||||||
|
mMonth = if (mMonth == 1) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
mMonth - 1
|
||||||
|
}
|
||||||
|
mDay = dateSplit[2].toInt()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
mYear = mCurrentDate[Calendar.YEAR]
|
||||||
|
mMonth = mCurrentDate[Calendar.MONTH]
|
||||||
|
mDay = mCurrentDate[Calendar.DAY_OF_MONTH]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mYear = mCurrentDate[Calendar.YEAR]
|
||||||
|
mMonth = mCurrentDate[Calendar.MONTH]
|
||||||
|
mDay = mCurrentDate[Calendar.DAY_OF_MONTH]
|
||||||
|
}
|
||||||
|
val mDatePicker = DatePickerDialog(
|
||||||
|
(this.context),
|
||||||
|
{ _, selectedYear, selectedMonth, selectedDay ->
|
||||||
|
var currentMonth = selectedMonth
|
||||||
|
val dateString = StringBuilder().append(selectedYear).append("-")
|
||||||
|
.append(String.format("%02d", (currentMonth + 1.also { currentMonth = it })))
|
||||||
|
.append("-")
|
||||||
|
.append(String.format("%02d", selectedDay))
|
||||||
|
.toString()
|
||||||
|
setText(dateString)
|
||||||
|
onSelected.invoke(dateString)
|
||||||
|
}, mYear, mMonth, mDay
|
||||||
|
)
|
||||||
|
mDatePicker.setTitle("Select date")
|
||||||
|
setOnClickListener {
|
||||||
|
mDatePicker.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.appttude.h_mal.farmr.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.appttude.h_mal.farmr.data.RepositoryImpl
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationViewModelFactory(
|
||||||
|
private val repository: RepositoryImpl
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
with(modelClass) {
|
||||||
|
return when {
|
||||||
|
isAssignableFrom(MainViewModel::class.java) -> MainViewModel(repository)
|
||||||
|
isAssignableFrom(SubmissionViewModel::class.java) -> SubmissionViewModel(repository)
|
||||||
|
isAssignableFrom(InfoViewModel::class.java) -> InfoViewModel(repository)
|
||||||
|
isAssignableFrom(FilterViewModel::class.java) -> FilterViewModel(repository)
|
||||||
|
else -> throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
|
} as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.appttude.h_mal.farmr.viewmodel
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.data.Repository
|
||||||
|
import com.appttude.h_mal.farmr.model.Success
|
||||||
|
|
||||||
|
|
||||||
|
class FilterViewModel(
|
||||||
|
repository: Repository
|
||||||
|
) : ShiftViewModel(repository) {
|
||||||
|
|
||||||
|
fun applyFilters(
|
||||||
|
description: String?,
|
||||||
|
dateFrom: String?,
|
||||||
|
dateTo: String?,
|
||||||
|
type: String?
|
||||||
|
) {
|
||||||
|
super.setFiltrationDetails(description, dateFrom, dateTo, type)
|
||||||
|
onSuccess(Success("Filter(s) have been applied"))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.appttude.h_mal.farmr.viewmodel
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.appttude.h_mal.farmr.data.Repository
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
|
||||||
|
import com.appttude.h_mal.farmr.utils.ID
|
||||||
|
|
||||||
|
|
||||||
|
class InfoViewModel(
|
||||||
|
repository: Repository
|
||||||
|
) : ShiftViewModel(repository) {
|
||||||
|
|
||||||
|
fun retrieveData(bundle: Bundle?) {
|
||||||
|
val id = bundle?.getLong(ID)
|
||||||
|
if (id == null) {
|
||||||
|
onError("Failed to retrieve shift")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val shift = getCurrentShift(id)
|
||||||
|
if (shift == null) {
|
||||||
|
onError("Failed to retrieve shift")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess(shift)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildDurationSummary(shiftObject: ShiftObject): String {
|
||||||
|
val time = shiftObject.getHoursMinutesPairFromDuration()
|
||||||
|
|
||||||
|
val stringBuilder = StringBuilder().append(time.first).append(" Hours ").append(time.second)
|
||||||
|
.append(" Minutes ")
|
||||||
|
if (shiftObject.breakMins > 0) {
|
||||||
|
stringBuilder.append(" (+ ").append(shiftObject.breakMins).append(" minutes break)")
|
||||||
|
}
|
||||||
|
return stringBuilder.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
package com.appttude.h_mal.farmr.viewmodel
|
||||||
|
|
||||||
|
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
import androidx.annotation.RequiresPermission
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import com.appttude.h_mal.farmr.data.Repository
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_BREAK
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DATE
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DESCRIPTION
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_DURATION
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_PAYRATE
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_IN
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TIME_OUT
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TOTALPAY
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_TYPE
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry.COLUMN_SHIFT_UNIT
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftsContract.ShiftsEntry._ID
|
||||||
|
import com.appttude.h_mal.farmr.model.Order
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import com.appttude.h_mal.farmr.model.Sortable
|
||||||
|
import com.appttude.h_mal.farmr.model.Success
|
||||||
|
import com.appttude.h_mal.farmr.utils.convertDateString
|
||||||
|
import com.appttude.h_mal.farmr.utils.formatAsCurrencyString
|
||||||
|
import com.appttude.h_mal.farmr.utils.sortedByOrder
|
||||||
|
import jxl.Workbook
|
||||||
|
import jxl.WorkbookSettings
|
||||||
|
import jxl.write.Label
|
||||||
|
import jxl.write.WritableWorkbook
|
||||||
|
import jxl.write.WriteException
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
class MainViewModel(
|
||||||
|
private val repository: Repository
|
||||||
|
) : ShiftViewModel(repository) {
|
||||||
|
|
||||||
|
private val _shiftLiveData = MutableLiveData<List<ShiftObject>>()
|
||||||
|
private val shiftLiveData: LiveData<List<ShiftObject>> = _shiftLiveData
|
||||||
|
|
||||||
|
private var mSort: Sortable = Sortable.ID
|
||||||
|
private var mOrder: Order = Order.ASCENDING
|
||||||
|
|
||||||
|
private val observer = Observer<List<ShiftObject>> {
|
||||||
|
it?.let {
|
||||||
|
val result = it.applyFilters().sortList(mSort, mOrder)
|
||||||
|
onSuccess(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Load shifts into live data when view model has been instantiated
|
||||||
|
refreshLiveData()
|
||||||
|
shiftLiveData.observeForever(observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<ShiftObject>.applyFilters(): List<ShiftObject> {
|
||||||
|
val filter = getFiltrationDetails()
|
||||||
|
|
||||||
|
return filter { s ->
|
||||||
|
comparedStrings(filter.type, s.type) &&
|
||||||
|
comparedStringsContains(filter.description, s.description) &&
|
||||||
|
(isBetween(filter.dateFrom, filter.dateTo, s.date) ?: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun comparedStrings(first: String?, second: String?): Boolean {
|
||||||
|
return when (compareValues(first, second)) {
|
||||||
|
-1, 0, 1 -> true
|
||||||
|
else -> {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun comparedStringsContains(first: String?, second: String?): Boolean {
|
||||||
|
first?.let {
|
||||||
|
(second?.contains(it))?.let { c -> return c }
|
||||||
|
}
|
||||||
|
|
||||||
|
return comparedStrings(first, second)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isBetween(fromDate: String?, toDate: String?, compareWith: String): Boolean? {
|
||||||
|
val first = fromDate?.convertDateString()
|
||||||
|
val second = toDate?.convertDateString()
|
||||||
|
|
||||||
|
if (first == null && second == null) return null
|
||||||
|
val compareDate = compareWith.convertDateString() ?: return null
|
||||||
|
|
||||||
|
if (second == null) return compareDate.after(first)
|
||||||
|
if (first == null) return compareDate.before(second)
|
||||||
|
|
||||||
|
return compareDate.after(first) && compareDate.before(second)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
shiftLiveData.removeObserver(observer)
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<ShiftObject>.sortList(sort: Sortable, order: Order): List<ShiftObject> {
|
||||||
|
return when (sort) {
|
||||||
|
Sortable.ID -> sortedByOrder(order) { it.id }
|
||||||
|
Sortable.TYPE -> sortedByOrder(order) { it.type }
|
||||||
|
Sortable.DATE -> sortedByOrder(order) { it.date }
|
||||||
|
Sortable.DESCRIPTION -> sortedByOrder(order) { it.description }
|
||||||
|
Sortable.DURATION -> sortedByOrder(order) { it.duration }
|
||||||
|
Sortable.UNITS -> sortedByOrder(order) { it.units }
|
||||||
|
Sortable.RATEOFPAY -> sortedByOrder(order) { it.rateOfPay }
|
||||||
|
Sortable.TOTALPAY -> sortedByOrder(order) { it.totalPay }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSortAndOrder(): Pair<Sortable, Order> {
|
||||||
|
return Pair(mSort, mOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSortAndOrder(sort: Sortable, order: Order = Order.ASCENDING) {
|
||||||
|
mSort = sort
|
||||||
|
mOrder = order
|
||||||
|
refreshLiveData()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInformation(): String {
|
||||||
|
var totalDuration = 0.0f
|
||||||
|
var countOfTypeH = 0
|
||||||
|
var countOfTypeP = 0
|
||||||
|
var totalUnits = 0f
|
||||||
|
var totalPay = 0f
|
||||||
|
var lines = 0
|
||||||
|
_shiftLiveData.value?.applyFilters()?.forEach {
|
||||||
|
lines += 1
|
||||||
|
totalDuration += it.duration
|
||||||
|
when (ShiftType.getEnumByType(it.type)) {
|
||||||
|
ShiftType.HOURLY -> countOfTypeH += 1
|
||||||
|
ShiftType.PIECE -> countOfTypeP += 1
|
||||||
|
}
|
||||||
|
totalUnits += it.units
|
||||||
|
totalPay += it.totalPay
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildInfoString(
|
||||||
|
totalDuration,
|
||||||
|
countOfTypeH,
|
||||||
|
countOfTypeP,
|
||||||
|
totalUnits,
|
||||||
|
totalPay,
|
||||||
|
lines
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteShift(id: Long) {
|
||||||
|
if (!repository.deleteSingleShiftFromDatabase(id)) {
|
||||||
|
onError("Failed to delete shift")
|
||||||
|
} else {
|
||||||
|
refreshLiveData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteAllShifts() {
|
||||||
|
if (!repository.deleteAllShiftsFromDatabase()) {
|
||||||
|
onError("Failed to delete all shifts from database")
|
||||||
|
} else {
|
||||||
|
refreshLiveData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildInfoString(
|
||||||
|
totalDuration: Float,
|
||||||
|
countOfHourly: Int,
|
||||||
|
countOfPiece: Int,
|
||||||
|
totalUnits: Float,
|
||||||
|
totalPay: Float,
|
||||||
|
lines: Int
|
||||||
|
): String {
|
||||||
|
val stringBuilder = StringBuilder("$lines Shifts").append("\n")
|
||||||
|
|
||||||
|
if (countOfHourly != 0 && countOfPiece != 0) {
|
||||||
|
stringBuilder.append(" ($countOfHourly Hourly/$countOfPiece Piece Rate)").append("\n")
|
||||||
|
}
|
||||||
|
if (countOfHourly != 0) {
|
||||||
|
stringBuilder.append("Total Hours: ").append(totalDuration).append("\n")
|
||||||
|
}
|
||||||
|
if (countOfPiece != 0) {
|
||||||
|
stringBuilder.append("Total Units: ").append(totalUnits).append("\n")
|
||||||
|
}
|
||||||
|
if (totalPay != 0f) {
|
||||||
|
stringBuilder.append("Total Pay: ").append(totalPay.formatAsCurrencyString())
|
||||||
|
}
|
||||||
|
return stringBuilder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshLiveData() {
|
||||||
|
repository.readShiftsFromDatabase()?.let { _shiftLiveData.postValue(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearFilters() {
|
||||||
|
super.setFiltrationDetails(null, null, null, null)
|
||||||
|
onSuccess(Success("Filters have been cleared"))
|
||||||
|
refreshLiveData()
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresPermission(WRITE_EXTERNAL_STORAGE)
|
||||||
|
fun createExcelSheet(file: File): File? {
|
||||||
|
val wbSettings = WorkbookSettings().apply {
|
||||||
|
locale = Locale("en", "EN")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val workbook: WritableWorkbook = Workbook.createWorkbook(file, wbSettings)
|
||||||
|
val sheet = workbook.createSheet("Shifts", 0)
|
||||||
|
// Write column headers
|
||||||
|
val headers = listOf(
|
||||||
|
Label(0, 0, _ID),
|
||||||
|
Label(1, 0, COLUMN_SHIFT_TYPE),
|
||||||
|
Label(2, 0, COLUMN_SHIFT_DESCRIPTION),
|
||||||
|
Label(3, 0, COLUMN_SHIFT_DATE),
|
||||||
|
Label(4, 0, COLUMN_SHIFT_TIME_IN),
|
||||||
|
Label(5, 0, COLUMN_SHIFT_TIME_OUT),
|
||||||
|
Label(6, 0, "$COLUMN_SHIFT_BREAK (in mins)"),
|
||||||
|
Label(7, 0, COLUMN_SHIFT_DURATION),
|
||||||
|
Label(8, 0, COLUMN_SHIFT_UNIT),
|
||||||
|
Label(9, 0, COLUMN_SHIFT_PAYRATE),
|
||||||
|
Label(10, 0, COLUMN_SHIFT_TOTALPAY)
|
||||||
|
)
|
||||||
|
// table content
|
||||||
|
if (shiftLiveData.value.isNullOrEmpty()) {
|
||||||
|
onError("No data to parse into excel file")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val sortAndOrder = getSortAndOrder()
|
||||||
|
val data = shiftLiveData.value!!.applyFilters()
|
||||||
|
.sortList(sortAndOrder.first, sortAndOrder.second)
|
||||||
|
var currentRow = 0
|
||||||
|
val cells = data.map { shift ->
|
||||||
|
currentRow += 1
|
||||||
|
listOf(
|
||||||
|
Label(0, currentRow, shift.id.toString()),
|
||||||
|
Label(1, currentRow, shift.type),
|
||||||
|
Label(2, currentRow, shift.description),
|
||||||
|
Label(3, currentRow, shift.date),
|
||||||
|
Label(4, currentRow, shift.timeIn),
|
||||||
|
Label(5, currentRow, shift.timeOut),
|
||||||
|
Label(6, currentRow, shift.breakMins.toString()),
|
||||||
|
Label(7, currentRow, shift.duration.toString()),
|
||||||
|
Label(8, currentRow, shift.units.toString()),
|
||||||
|
Label(9, currentRow, shift.rateOfPay.toString()),
|
||||||
|
Label(10, currentRow, shift.totalPay.toString())
|
||||||
|
)
|
||||||
|
}.flatten()
|
||||||
|
|
||||||
|
currentRow += 1
|
||||||
|
val footer = listOf(
|
||||||
|
Label(0, currentRow, "Total:"),
|
||||||
|
Label(7, currentRow, data.sumOf { it.duration.toDouble() }.toString()),
|
||||||
|
Label(8, currentRow, data.sumOf { it.units.toDouble() }.toString()),
|
||||||
|
Label(10, currentRow, data.sumOf { it.totalPay.toDouble() }.toString())
|
||||||
|
)
|
||||||
|
val content = listOf(headers, cells, footer).flatten()
|
||||||
|
|
||||||
|
// Write content to sheet
|
||||||
|
try {
|
||||||
|
content.forEach { c -> sheet.addCell(c) }
|
||||||
|
} catch (e: WriteException) {
|
||||||
|
onError("Failed to write excel sheet")
|
||||||
|
return null
|
||||||
|
} catch (e: WriteException) {
|
||||||
|
onError("Failed to write excel sheet")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
workbook.write()
|
||||||
|
workbook.close()
|
||||||
|
|
||||||
|
return file
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
onError("Failed to generate excel sheet of shifts")
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.appttude.h_mal.farmr.viewmodel
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.base.BaseViewModel
|
||||||
|
import com.appttude.h_mal.farmr.data.Repository
|
||||||
|
import com.appttude.h_mal.farmr.data.prefs.DATE_IN
|
||||||
|
import com.appttude.h_mal.farmr.data.prefs.DATE_OUT
|
||||||
|
import com.appttude.h_mal.farmr.data.prefs.DESCRIPTION
|
||||||
|
import com.appttude.h_mal.farmr.data.prefs.TYPE
|
||||||
|
import com.appttude.h_mal.farmr.model.FilterStore
|
||||||
|
|
||||||
|
|
||||||
|
open class ShiftViewModel(
|
||||||
|
private val repository: Repository
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add Item & Further info
|
||||||
|
*/
|
||||||
|
fun getCurrentShift(id: Long) = repository.readSingleShiftFromDatabase(id)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lambda function that will invoke onError(...) on failure
|
||||||
|
* but update live data when successful
|
||||||
|
*/
|
||||||
|
private inline fun doTry(operation: () -> Unit) {
|
||||||
|
try {
|
||||||
|
operation.invoke()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun setFiltrationDetails(
|
||||||
|
description: String?,
|
||||||
|
dateFrom: String?,
|
||||||
|
dateTo: String?,
|
||||||
|
type: String?
|
||||||
|
) {
|
||||||
|
repository.setFilteringDetailsInPrefs(description, dateFrom, dateTo, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun getFiltrationDetails(): FilterStore {
|
||||||
|
val prefs = repository.retrieveFilteringDetailsInPrefs()
|
||||||
|
return FilterStore(
|
||||||
|
prefs[DESCRIPTION],
|
||||||
|
prefs[DATE_IN],
|
||||||
|
prefs[DATE_OUT],
|
||||||
|
prefs[TYPE]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
package com.appttude.h_mal.farmr.viewmodel
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.data.Repository
|
||||||
|
import com.appttude.h_mal.farmr.model.Shift
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import com.appttude.h_mal.farmr.model.Success
|
||||||
|
import com.appttude.h_mal.farmr.utils.calculateDuration
|
||||||
|
import com.appttude.h_mal.farmr.utils.dateStringIsValid
|
||||||
|
import com.appttude.h_mal.farmr.utils.formatToTwoDp
|
||||||
|
import com.appttude.h_mal.farmr.utils.getTimeString
|
||||||
|
import com.appttude.h_mal.farmr.utils.timeStringIsValid
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
|
||||||
|
class SubmissionViewModel(
|
||||||
|
private val repository: Repository
|
||||||
|
) : ShiftViewModel(repository) {
|
||||||
|
|
||||||
|
fun insertHourlyShift(
|
||||||
|
description: String,
|
||||||
|
date: String,
|
||||||
|
rateOfPay: Float,
|
||||||
|
timeIn: String?,
|
||||||
|
timeOut: String?,
|
||||||
|
breakMins: Int?,
|
||||||
|
) {
|
||||||
|
// Validate inputs from the edit texts
|
||||||
|
(description.length > 3).validateField {
|
||||||
|
onError("Description length should be longer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
date.dateStringIsValid().validateField {
|
||||||
|
onError("Date format is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(rateOfPay >= 0.00).validateField {
|
||||||
|
onError("Rate of pay is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timeIn?.timeStringIsValid()?.validateField {
|
||||||
|
onError("Time in format is in correct")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timeOut?.timeStringIsValid()?.validateField {
|
||||||
|
onError("Time out format is in correct")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
breakMins?.let { it >= 0 }?.validateField {
|
||||||
|
onError("Break in minutes is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = insertShiftIntoDatabase(
|
||||||
|
ShiftType.HOURLY,
|
||||||
|
description,
|
||||||
|
date,
|
||||||
|
rateOfPay.formatToTwoDp(),
|
||||||
|
timeIn,
|
||||||
|
timeOut,
|
||||||
|
breakMins,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result) onSuccess(Success("New shift successfully added"))
|
||||||
|
else onError("Cannot insert shift")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insertPieceRateShift(
|
||||||
|
description: String,
|
||||||
|
date: String,
|
||||||
|
units: Float,
|
||||||
|
rateOfPay: Float
|
||||||
|
) {
|
||||||
|
// Validate inputs from the edit texts
|
||||||
|
(description.length > 3).validateField {
|
||||||
|
onError("Description length should be longer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
date.dateStringIsValid().validateField {
|
||||||
|
onError("Date format is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(rateOfPay >= 0.00).validateField {
|
||||||
|
onError("Rate of pay is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(units.toInt() >= 0).validateField {
|
||||||
|
onError("Units cannot be below zero")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = insertShiftIntoDatabase(
|
||||||
|
type = ShiftType.PIECE,
|
||||||
|
description = description,
|
||||||
|
date = date,
|
||||||
|
rateOfPay = rateOfPay.formatToTwoDp(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
units = units
|
||||||
|
)
|
||||||
|
if (result) onSuccess(Success("New shift successfully added"))
|
||||||
|
else onError("Cannot insert shift")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateShift(
|
||||||
|
id: Long,
|
||||||
|
type: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
date: String? = null,
|
||||||
|
rateOfPay: Float? = null,
|
||||||
|
timeIn: String? = null,
|
||||||
|
timeOut: String? = null,
|
||||||
|
breakMins: Int? = null,
|
||||||
|
units: Float? = null,
|
||||||
|
) {
|
||||||
|
description?.let {
|
||||||
|
(it.length > 3).validateField {
|
||||||
|
onError("Description length should be longer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
date?.dateStringIsValid()?.validateField {
|
||||||
|
onError("Date format is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rateOfPay?.let {
|
||||||
|
(it >= 0.00).validateField {
|
||||||
|
onError("Rate of pay is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
units?.let {
|
||||||
|
(it.toInt() >= 0).validateField {
|
||||||
|
onError("Units cannot be below zero")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeIn?.timeStringIsValid()?.validateField {
|
||||||
|
onError("Time in format is in correct")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timeOut?.timeStringIsValid()?.validateField {
|
||||||
|
onError("Time out format is in correct")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
breakMins?.let { it >= 0 }?.validateField {
|
||||||
|
onError("Break in minutes is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = updateShiftInDatabase(
|
||||||
|
id,
|
||||||
|
type = type?.let { ShiftType.getEnumByType(it) },
|
||||||
|
description = description,
|
||||||
|
date = date,
|
||||||
|
rateOfPay = rateOfPay,
|
||||||
|
timeIn = timeIn,
|
||||||
|
timeOut = timeOut,
|
||||||
|
breakMins = breakMins,
|
||||||
|
units = units
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result) onSuccess(Success("Shift successfully updated"))
|
||||||
|
else onError("Cannot update shift")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateShiftInDatabase(
|
||||||
|
id: Long,
|
||||||
|
type: ShiftType? = null,
|
||||||
|
description: String? = null,
|
||||||
|
date: String? = null,
|
||||||
|
rateOfPay: Float? = null,
|
||||||
|
timeIn: String? = null,
|
||||||
|
timeOut: String? = null,
|
||||||
|
breakMins: Int? = null,
|
||||||
|
units: Float? = null,
|
||||||
|
): Boolean {
|
||||||
|
val currentShift = repository.readSingleShiftFromDatabase(id)?.copyToShift()
|
||||||
|
?: throw IOException("Cannot update shift as it does not exist")
|
||||||
|
|
||||||
|
val shift = when (type) {
|
||||||
|
ShiftType.HOURLY -> {
|
||||||
|
// Shift type has changed so mandatory fields for hourly shift are now required as well
|
||||||
|
val insertTimeIn =
|
||||||
|
(timeIn ?: currentShift.timeIn) ?: throw IOException("No time in inserted")
|
||||||
|
val insertTimeOut =
|
||||||
|
(timeOut ?: currentShift.timeOut) ?: throw IOException("No time out inserted")
|
||||||
|
Shift(
|
||||||
|
description = description ?: currentShift.description,
|
||||||
|
date = date ?: currentShift.date,
|
||||||
|
timeIn = insertTimeIn,
|
||||||
|
timeOut = insertTimeOut,
|
||||||
|
breakMins = breakMins ?: currentShift.breakMins,
|
||||||
|
rateOfPay = rateOfPay ?: currentShift.rateOfPay
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ShiftType.PIECE -> {
|
||||||
|
// Shift type has changed so mandatory fields for piece rate shift are now required as well
|
||||||
|
val insertUnits = (units ?: currentShift.units)
|
||||||
|
?: throw IOException("Units must be inserted for piece rate shifts")
|
||||||
|
Shift(
|
||||||
|
description = description ?: currentShift.description,
|
||||||
|
date = date ?: currentShift.date,
|
||||||
|
units = insertUnits,
|
||||||
|
rateOfPay = rateOfPay ?: currentShift.rateOfPay
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
if (timeIn == null && timeOut == null && units == null && breakMins == null && rateOfPay == null) {
|
||||||
|
// Updates to description or date field
|
||||||
|
currentShift.copy(
|
||||||
|
description = description ?: currentShift.description,
|
||||||
|
date = date ?: currentShift.date,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Updating shifts where shift type has remained the same
|
||||||
|
when (currentShift.type) {
|
||||||
|
ShiftType.HOURLY -> {
|
||||||
|
val insertTimeIn = (timeIn ?: currentShift.timeIn) ?: throw IOException(
|
||||||
|
"No time in inserted"
|
||||||
|
)
|
||||||
|
val insertTimeOut = (timeOut ?: currentShift.timeOut)
|
||||||
|
?: throw IOException("No time out inserted")
|
||||||
|
Shift(
|
||||||
|
description = description ?: currentShift.description,
|
||||||
|
date = date ?: currentShift.date,
|
||||||
|
timeIn = insertTimeIn,
|
||||||
|
timeOut = insertTimeOut,
|
||||||
|
breakMins = breakMins ?: currentShift.breakMins,
|
||||||
|
rateOfPay = rateOfPay ?: currentShift.rateOfPay
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ShiftType.PIECE -> {
|
||||||
|
val insertUnits = (units ?: currentShift.units)
|
||||||
|
?: throw IOException("Units must be inserted for piece rate shifts")
|
||||||
|
Shift(
|
||||||
|
description = description ?: currentShift.description,
|
||||||
|
date = date ?: currentShift.date,
|
||||||
|
units = insertUnits,
|
||||||
|
rateOfPay = rateOfPay ?: currentShift.rateOfPay
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return repository.updateShiftIntoDatabase(id, shift)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun insertShiftIntoDatabase(
|
||||||
|
type: ShiftType,
|
||||||
|
description: String,
|
||||||
|
date: String,
|
||||||
|
rateOfPay: Float,
|
||||||
|
timeIn: String?,
|
||||||
|
timeOut: String?,
|
||||||
|
breakMins: Int?,
|
||||||
|
units: Float?,
|
||||||
|
): Boolean {
|
||||||
|
val shift = when (type) {
|
||||||
|
ShiftType.HOURLY -> {
|
||||||
|
if (timeIn.isNullOrBlank() && timeOut.isNullOrBlank()) throw IOException("Time in and time out are null")
|
||||||
|
val calendar by lazy { Calendar.getInstance() }
|
||||||
|
val insertTimeIn = timeIn ?: calendar.getTimeString()
|
||||||
|
val insertTimeOut = timeOut ?: calendar.getTimeString()
|
||||||
|
Shift(
|
||||||
|
description = description,
|
||||||
|
date = date,
|
||||||
|
timeIn = insertTimeIn,
|
||||||
|
timeOut = insertTimeOut,
|
||||||
|
breakMins = breakMins,
|
||||||
|
rateOfPay = rateOfPay
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ShiftType.PIECE -> {
|
||||||
|
Shift(
|
||||||
|
description = description,
|
||||||
|
date = date,
|
||||||
|
units = units!!,
|
||||||
|
rateOfPay = rateOfPay,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return repository.insertShiftIntoDatabase(shift)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun Boolean.validateField(failureCallback: () -> Unit) {
|
||||||
|
if (!this) failureCallback.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun retrieveDurationText(mTimeIn: String?, mTimeOut: String?, mBreaks: Int?): Float? {
|
||||||
|
try {
|
||||||
|
return calculateDuration(mTimeIn, mTimeOut, mBreaks)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
onError(e)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 90 KiB |
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector
|
|
||||||
android:height="108dp"
|
|
||||||
android:width="108dp"
|
|
||||||
android:viewportHeight="108"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="#3DDC84"
|
|
||||||
android:pathData="M0,0h108v108h-108z"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector
|
|
||||||
android:height="108dp"
|
|
||||||
android:width="108dp"
|
|
||||||
android:viewportHeight="108"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="#3DDC84"
|
|
||||||
android:pathData="M0,0h108v108h-108z"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
|
||||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-0.25 1.97,-0.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z" />
|
|
||||||
</vector>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
38
app/src/main/res/layout/empty_list_view.xml
Normal file
38
app/src/main/res/layout/empty_list_view.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<RelativeLayout
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
android:id="@+id/empty_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/empty_title_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:text="Shift list empty"
|
||||||
|
android:textAppearance="?android:textAppearanceMedium"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/empty_subtitle_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/empty_title_text"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:text="add shift to begin"
|
||||||
|
android:textAppearance="?android:textAppearanceSmall"
|
||||||
|
android:textColor="#A2AAB0"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -8,16 +8,9 @@
|
|||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
tools:context="com.appttude.h_mal.farmr.FragmentAddItem"
|
tools:context="com.appttude.h_mal.farmr.ui.FragmentAddItem"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/pd_ai"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@@ -189,7 +182,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="Break"
|
android:text="@string/break_res"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat" />
|
android:textAppearance="@style/TextAppearance.AppCompat" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
@@ -198,7 +191,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="2"
|
android:layout_weight="2"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:hint="Break in minutes"
|
android:hint="@string/insert_break_in_minutes"
|
||||||
android:inputType="number"
|
android:inputType="number"
|
||||||
android:selectAllOnFocus="true" />
|
android:selectAllOnFocus="true" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
tools:context="com.appttude.h_mal.farmr.FilterDataFragment">
|
tools:context="com.appttude.h_mal.farmr.ui.FilterDataFragment">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
tools:context="com.appttude.h_mal.farmr.FurtherInfoFragment">
|
tools:context="com.appttude.h_mal.farmr.ui.FurtherInfoFragment">
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:id="@+id/recycler">
|
|
||||||
|
|
||||||
</androidx.recyclerview.widget.RecyclerView>
|
|
||||||
</FrameLayout>
|
|
||||||
@@ -2,44 +2,16 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
xmlns:ads="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context="com.appttude.h_mal.farmr.FragmentMain">
|
tools:context="com.appttude.h_mal.farmr.ui.FragmentMain">
|
||||||
|
|
||||||
<ListView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/list_item_view"
|
android:id="@+id/list_item_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
tools:listitem="@layout/list_item_1">
|
tools:listitem="@layout/list_item_1">
|
||||||
</ListView>
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/empty_view"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerInParent="true">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/empty_title_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:fontFamily="sans-serif-medium"
|
|
||||||
android:paddingTop="16dp"
|
|
||||||
android:text="Shift list empty"
|
|
||||||
android:textAppearance="?android:textAppearanceMedium"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/empty_subtitle_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/empty_title_text"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:fontFamily="sans-serif"
|
|
||||||
android:paddingTop="8dp"
|
|
||||||
android:text="add shift to begin"
|
|
||||||
android:textAppearance="?android:textAppearanceSmall"
|
|
||||||
android:textColor="#A2AAB0"/>
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fab1"
|
android:id="@+id/fab1"
|
||||||
@@ -49,6 +21,11 @@
|
|||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_margin="@dimen/fab_margin"
|
android:layout_margin="@dimen/fab_margin"
|
||||||
android:src="@drawable/add"
|
android:src="@drawable/add"
|
||||||
ads:backgroundTint="@color/colorPrimary" />
|
app:backgroundTint="@color/colorPrimary" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:visibility="gone"
|
||||||
|
layout="@layout/empty_list_view"
|
||||||
|
android:id="@+id/empty_view"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -126,7 +126,6 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
|
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
app:srcCompat="@android:drawable/ic_menu_edit" />
|
app:srcCompat="@android:drawable/ic_menu_edit" />
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context=".MainActivity"
|
tools:context=".ui.MainActivity"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
tools:context="com.appttude.h_mal.farmr.MainActivity">
|
tools:context="com.appttude.h_mal.farmr.ui.MainActivity">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_favorite"
|
android:id="@+id/action_favorite"
|
||||||
android:icon="@drawable/image_i_64"
|
android:icon="@drawable/image_i_64"
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Farmr</string>
|
<string name="app_name">Farmr</string>
|
||||||
<string name="action_settings">Settings</string>
|
|
||||||
<string name="category_ach">Shifts</string>
|
<string name="category_ach">Shifts</string>
|
||||||
|
|
||||||
<string name="add_item_title">Add Shift</string>
|
<string name="add_item_title">Add Shift</string>
|
||||||
<string name="edit_item_title">Edit Shift</string>
|
<string name="edit_item_title">Edit Shift</string>
|
||||||
<string name="delete_item">Delete Shift</string>
|
|
||||||
<string name="insert_item_failed">failed to insert Shift</string>
|
<string name="insert_item_failed">failed to insert Shift</string>
|
||||||
<string name="insert_item_successful">Shift successfully added</string>
|
<string name="insert_item_successful">Shift successfully added</string>
|
||||||
<string name="update_item_failed">Update Failed</string>
|
<string name="update_item_failed">Update Failed</string>
|
||||||
@@ -25,11 +23,6 @@
|
|||||||
<!-- Example General settings -->
|
<!-- Example General settings -->
|
||||||
<string name="pref_header_general">General</string>
|
<string name="pref_header_general">General</string>
|
||||||
|
|
||||||
<string name="pref_title_social_recommendations">Enable social recommendations</string>
|
|
||||||
<string name="pref_description_social_recommendations">Recommendations for people to contact
|
|
||||||
based on your message history
|
|
||||||
</string>
|
|
||||||
|
|
||||||
<string name="pref_title_display_name">Display name</string>
|
<string name="pref_title_display_name">Display name</string>
|
||||||
<string name="pref_default_display_name">John Smith</string>
|
<string name="pref_default_display_name">John Smith</string>
|
||||||
|
|
||||||
@@ -102,4 +95,6 @@
|
|||||||
<!-- TODO: Remove or change this placeholder text -->
|
<!-- TODO: Remove or change this placeholder text -->
|
||||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||||
<string name="further_info_title">Shift Details</string>
|
<string name="further_info_title">Shift Details</string>
|
||||||
|
<string name="insert_break_in_minutes">insert break in minutes</string>
|
||||||
|
<string name="break_res">Break</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
4
app/src/main/res/xml/provider_path.xml
Normal file
4
app/src/main/res/xml/provider_path.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<external-path name="external_files" path="."/>
|
||||||
|
</paths>
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.appttude.h_mal.farmr;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
public class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
public void addition_isCorrect() throws Exception {
|
|
||||||
assertEquals(4, 2 + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.appttude.h_mal.farmr.data
|
||||||
|
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.LegacyDatabase
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
|
||||||
|
import com.appttude.h_mal.farmr.data.prefs.PreferenceProvider
|
||||||
|
import io.mockk.MockKAnnotations
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.impl.annotations.MockK
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.ArgumentMatchers.anyLong
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertIs
|
||||||
|
|
||||||
|
class RepositoryImplTest {
|
||||||
|
|
||||||
|
private lateinit var repository: RepositoryImpl
|
||||||
|
|
||||||
|
@MockK
|
||||||
|
lateinit var db: LegacyDatabase
|
||||||
|
|
||||||
|
@MockK
|
||||||
|
lateinit var prefs: PreferenceProvider
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockKAnnotations.init(this)
|
||||||
|
repository = RepositoryImpl(db, prefs)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun readDatabase_validResponse() {
|
||||||
|
// Arrange
|
||||||
|
val elements = listOf<ShiftObject>(
|
||||||
|
mockk { every { id } returns anyLong() },
|
||||||
|
mockk { every { id } returns anyLong() },
|
||||||
|
mockk { every { id } returns anyLong() },
|
||||||
|
mockk { every { id } returns anyLong() }
|
||||||
|
)
|
||||||
|
|
||||||
|
//Act
|
||||||
|
every { db.readShiftsFromDatabase() } returns elements
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
val result = repository.readShiftsFromDatabase()
|
||||||
|
assertIs<List<ShiftObject>>(result)
|
||||||
|
assertEquals(result.first().id, anyLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
149
app/src/test/java/com/appttude/h_mal/farmr/utils/testUtils.kt
Normal file
149
app/src/test/java/com/appttude/h_mal/farmr/utils/testUtils.kt
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package com.appttude.h_mal.farmr.utils
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
|
||||||
|
import com.appttude.h_mal.farmr.model.ShiftType
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.mockito.ArgumentMatchers
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
fun <T> LiveData<T>.getOrAwaitValue(
|
||||||
|
time: Long = 2,
|
||||||
|
timeUnit: TimeUnit = TimeUnit.SECONDS
|
||||||
|
): T {
|
||||||
|
var data: T? = null
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
val observer = object : Observer<T> {
|
||||||
|
override fun onChanged(o: T?) {
|
||||||
|
data = o
|
||||||
|
latch.countDown()
|
||||||
|
this@getOrAwaitValue.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.observeForever(observer)
|
||||||
|
|
||||||
|
// Don't wait indefinitely if the LiveData is not set.
|
||||||
|
if (!latch.await(time, timeUnit)) {
|
||||||
|
throw TimeoutException("LiveData value was never set.")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return data as T
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sleep(millis: Long = 1000) {
|
||||||
|
runBlocking(Dispatchers.Default) { delay(millis) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getShifts() = listOf(
|
||||||
|
ShiftObject(
|
||||||
|
ArgumentMatchers.anyLong(),
|
||||||
|
ShiftType.HOURLY.type,
|
||||||
|
"Day one",
|
||||||
|
"2023-08-01",
|
||||||
|
"12:00",
|
||||||
|
"13:00",
|
||||||
|
1f,
|
||||||
|
ArgumentMatchers.anyInt(),
|
||||||
|
ArgumentMatchers.anyFloat(),
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
ShiftObject(
|
||||||
|
ArgumentMatchers.anyLong(),
|
||||||
|
ShiftType.HOURLY.type,
|
||||||
|
"Day two",
|
||||||
|
"2023-08-02",
|
||||||
|
"12:00",
|
||||||
|
"13:00",
|
||||||
|
1f,
|
||||||
|
ArgumentMatchers.anyInt(),
|
||||||
|
ArgumentMatchers.anyFloat(),
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
ShiftObject(
|
||||||
|
ArgumentMatchers.anyLong(),
|
||||||
|
ShiftType.HOURLY.type,
|
||||||
|
"Day three",
|
||||||
|
"2023-08-03",
|
||||||
|
"12:00",
|
||||||
|
"13:00",
|
||||||
|
1f,
|
||||||
|
30,
|
||||||
|
ArgumentMatchers.anyFloat(),
|
||||||
|
10f,
|
||||||
|
5f
|
||||||
|
),
|
||||||
|
ShiftObject(
|
||||||
|
ArgumentMatchers.anyLong(),
|
||||||
|
ShiftType.HOURLY.type,
|
||||||
|
"Day four",
|
||||||
|
"2023-08-04",
|
||||||
|
"12:00",
|
||||||
|
"13:00",
|
||||||
|
1f,
|
||||||
|
30,
|
||||||
|
ArgumentMatchers.anyFloat(),
|
||||||
|
10f,
|
||||||
|
5f
|
||||||
|
),
|
||||||
|
ShiftObject(
|
||||||
|
ArgumentMatchers.anyLong(),
|
||||||
|
ShiftType.PIECE.type,
|
||||||
|
"Day five",
|
||||||
|
"2023-08-05",
|
||||||
|
ArgumentMatchers.anyString(),
|
||||||
|
ArgumentMatchers.anyString(),
|
||||||
|
ArgumentMatchers.anyFloat(),
|
||||||
|
ArgumentMatchers.anyInt(),
|
||||||
|
1f,
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
ShiftObject(
|
||||||
|
ArgumentMatchers.anyLong(),
|
||||||
|
ShiftType.PIECE.type,
|
||||||
|
"Day six",
|
||||||
|
"2023-08-06",
|
||||||
|
ArgumentMatchers.anyString(),
|
||||||
|
ArgumentMatchers.anyString(),
|
||||||
|
ArgumentMatchers.anyFloat(),
|
||||||
|
ArgumentMatchers.anyInt(),
|
||||||
|
1f,
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
ShiftObject(
|
||||||
|
ArgumentMatchers.anyLong(),
|
||||||
|
ShiftType.PIECE.type,
|
||||||
|
"Day seven",
|
||||||
|
"2023-08-07",
|
||||||
|
ArgumentMatchers.anyString(),
|
||||||
|
ArgumentMatchers.anyString(),
|
||||||
|
ArgumentMatchers.anyFloat(),
|
||||||
|
ArgumentMatchers.anyInt(),
|
||||||
|
1f,
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
ShiftObject(
|
||||||
|
ArgumentMatchers.anyLong(),
|
||||||
|
ShiftType.PIECE.type,
|
||||||
|
"Day eight",
|
||||||
|
"2023-08-08",
|
||||||
|
ArgumentMatchers.anyString(),
|
||||||
|
ArgumentMatchers.anyString(),
|
||||||
|
ArgumentMatchers.anyFloat(),
|
||||||
|
ArgumentMatchers.anyInt(),
|
||||||
|
1f,
|
||||||
|
10f,
|
||||||
|
10f
|
||||||
|
),
|
||||||
|
)
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.appttude.h_mal.farmr.viewmodel
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.appttude.h_mal.farmr.data.legacydb.ShiftObject
|
||||||
|
import com.appttude.h_mal.farmr.utils.ID
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.ArgumentMatchers.anyLong
|
||||||
|
import kotlin.test.assertIs
|
||||||
|
|
||||||
|
class InfoViewModelTest : ShiftViewModelTest<InfoViewModel>() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun retrieveData_validBundleAndId_successfulRetrieval() {
|
||||||
|
// Arrange
|
||||||
|
val id = anyLong()
|
||||||
|
val shift = mockk<ShiftObject>()
|
||||||
|
val bundle = mockk<Bundle>()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
every { repository.readSingleShiftFromDatabase(id) }.returns(shift)
|
||||||
|
every { bundle.getLong(ID) }.returns(id)
|
||||||
|
viewModel.retrieveData(bundle)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertIs<ShiftObject>(retrieveCurrentData())
|
||||||
|
assertEquals(
|
||||||
|
retrieveCurrentData(),
|
||||||
|
shift
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun retrieveData_noValidBundleAndId_unsuccessfulRetrieval() {
|
||||||
|
// Arrange
|
||||||
|
val id = anyLong()
|
||||||
|
val shift = mockk<ShiftObject>()
|
||||||
|
val bundle = mockk<Bundle>()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
every { repository.readSingleShiftFromDatabase(id) }.returns(shift)
|
||||||
|
every { bundle.getLong(ID) }.returns(id)
|
||||||
|
viewModel.retrieveData(null)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(
|
||||||
|
retrieveCurrentError(),
|
||||||
|
"Failed to retrieve shift"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun retrieveData_validBundleNoShift_successfulRetrieval() {
|
||||||
|
// Arrange
|
||||||
|
val id = anyLong()
|
||||||
|
val bundle = mockk<Bundle>()
|
||||||
|
|
||||||
|
// Act
|
||||||
|
every { repository.readSingleShiftFromDatabase(id) }.returns(null)
|
||||||
|
every { bundle.getLong(ID) }.returns(id)
|
||||||
|
viewModel.retrieveData(bundle)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(
|
||||||
|
retrieveCurrentError(),
|
||||||
|
"Failed to retrieve shift"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun buildDurationSummary_validHourlyShift_successfulRetrieval() {
|
||||||
|
// Arrange
|
||||||
|
val shift = getShifts()[0]
|
||||||
|
val shiftWithBreak = getShifts()[3]
|
||||||
|
|
||||||
|
// Act
|
||||||
|
val summary = viewModel.buildDurationSummary(shift)
|
||||||
|
val summaryWithBreak = viewModel.buildDurationSummary(shiftWithBreak)
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(
|
||||||
|
"1 Hours 0 Minutes ",
|
||||||
|
summary
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
"1 Hours 0 Minutes (+ 30 minutes break)",
|
||||||
|
summaryWithBreak
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user