Merge 28/06/2023 (#29)

* - updated config.yml
 - Fastlane added
 - lanes added for upload to playstore

Took 3 hours 34 minutes

* - refactoring of admin app
 - file structure updated

Took 34 hours 38 minutes

* - minor bug fixes

Took 3 hours 24 minutes

* - mid commit

Took 4 hours 2 minutes

* - mid commit

* - mid commit

Took 8 hours 5 minutes

* - mid commit

* - mid commit

* - mid commit

Took 55 minutes

* - Robots added for tests
 - Tests added
 - view naming refactoring
 - Image selecting stubbing added

Took 3 hours 48 minutes

* - code clean up

* first commit

* Update driver-8f4a1.json

* - Approver for documents as Admin (#19)

 - Approver for documents as Admin
 - UI tests for document approving
 - update config.yml
 - update android test suite
 - idling resources added for toast
 - toast methods refactored
 - tests for approving updated

* Driver admin complete
 - empty view for no users
 - edit user identifier
 - test for driver admin added

* Driver admin complete
 - update carousel version

* Driver admin complete
 - update carousel version

* mid commit

* fix failing tests

* Updated config.yml

* - screenshot library setup
 - pull screenshot after tests
 - update to fastfile to produce bundles instead of .apks

Took 4 hours 17 minutes

* - config.yml updated

Took 37 minutes

* Updated config.yml

* Updated config.yml

* Updated config.yml

* Updated config.yml

* Updated config.yml

* Updated config.yml

* Updated config.yml

* Updated config.yml

* fix failing tests

* fix failing tests

* - lint checks

* Circleci update (#30)

 - dirty fix for failing test

* Updated to circleci (#31)

circleci config.yml updated

* Circleci branch deployment fix (#32)

- circleci Local testing
- circleci deployment fixed
- submodule circleci fix
This commit is contained in:
2023-08-05 16:41:08 +01:00
committed by GitHub
parent 23f16626cd
commit 1c7619fe38
227 changed files with 3902 additions and 2187 deletions

View File

@@ -7,8 +7,135 @@ version: 2.1
# 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. # 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/2.0/orb-intro/ # See: https://circleci.com/docs/2.0/orb-intro/
orbs: orbs:
android: circleci/android@1.0.3 android: circleci/android@2.3.0
commands:
setup_repo:
description: checkout repo and android dependencies
steps:
- checkout
# Setup files for build.
- run:
name: Setup variables for build
command: |
echo "$GOOGLE_SERVICES_KEY" > "app/google-services.json"
- android/restore-gradle-cache
build_gradle:
description: Build the gradle
steps:
- android/restore-gradle-cache
- run:
name: Download Dependencies
command: |
sudo chmod +x ./gradlew
./gradlew androidDependencies
- android/save-gradle-cache
run_tests:
description: run non-instrumentation tests for flavour specified
parameters:
flavour:
type: string
default: "Driver"
steps:
# The next step will run the unit tests
- build_gradle
- run:
name: Run non-instrumentation unit tests
command: |
./gradlew test<< parameters.flavour >>DebugUnitTest --continue
- store_artifacts:
path: app/build/reports
destination: reports
- store_test_results:
path: app/build/test-results
run_ui_tests:
description: start firebase emulator and run ui tests for flavour specified
parameters:
flavour:
type: string
default: "AtlasWeather"
steps:
# Download and cache dependencies
- build_gradle
- run:
name: Setup subtree for test data
command: |
git stash
git config --global user.email "$GIT_EMAIL"
git config --global user.name "$GIT_EMAIL"
git remote add -f driver_app_data https://github.com/hmalik144/driver_app_data.git
git subtree add --prefix=driver_app_data driver_app_data main --squash
- restore_cache:
keys:
- emulator-cache-v1-
# Install Firebase tools needed for firebase emulator
- run:
name: Install firebase tools
command: |
curl -sL firebase.tools | bash
# Then start the emulator and run the Instrumentation tests!
- android/start-emulator-and-run-tests:
post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest
test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest
system-image: system-images;android-25;google_apis;x86
pull-data: true
pull-data-path: /storage/emulated/0/Android/data/
pull-data-target: ~/app-data
pre-emulator-wait-steps:
# Start firebase emulator in the background while waiting to start testing
- run:
name: Start firebase emulator and while avd starts
command: |
firebase emulators:start --import=driver_app_data/export_directory
background: true
post-run-tests-steps:
# Save cache for firebase tools
- save_cache:
paths:
- ~/.cache/firebase/emulators/
key: emulator-cache-v1-{{ epoch }}
# store test reports
- store_artifacts:
path: app/build/reports/androidTests/connected
destination: reports
# store screenshots for failed ui tests
- store_artifacts:
path: ~/app-data
destination: data
# Then publish the artifacts of the Firebase emulator logs!
- run:
name: save firebase emulator logs
command: |
mkdir -p tmp/firebase_logs
cp *.log tmp/firebase_logs
- store_artifacts:
path: tmp/firebase_logs
destination: logs
# Then publish the results of the Instrumentation tests!
- store_test_results:
path: app/build/outputs/androidTest-results/connected
deploy_to_play_store:
description: deploy to playstore based on flavour
parameters:
flavour:
type: string
default: "Driver"
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"
- build_gradle
- run:
name: Run fastlane command to deploy to playstore
command: |
pwd
bundle exec fastlane deploy<< parameters.flavour >>
- store_test_results:
path: fastlane/report.xml
# Define a job to be invoked later in a workflow. # Define a job to be invoked later in a workflow.
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs # See: https://circleci.com/docs/2.0/configuration-reference/#jobs
jobs: jobs:
@@ -23,76 +150,30 @@ jobs:
# See: https://circleci.com/docs/2.0/executor-types/ # See: https://circleci.com/docs/2.0/executor-types/
executor: executor:
name: android/android-machine name: android/android-machine
tag: 2023.05.1
# Add steps to the job # Add steps to the job
# See: https://circleci.com/docs/2.0/configuration-reference/#steps # See: https://circleci.com/docs/2.0/configuration-reference/#steps
steps: steps:
# Checkout the code as the first step. # Checkout the code and its submodule as the first step.
- checkout - setup_repo
# Setup files for build. # - run_tests:
- run: # flavour: << parameters.flavour >>
name: Setup variables for build - run_ui_tests:
command: | flavour: << parameters.flavour >>
echo "$GOOGLE_SERVICES_KEY" > "app/google-services.json" deploy-to-playstore:
- run:
name: Grant execute permission for gradlew
command: |
chmod +x gradlew
# The next step will run the unit tests
- android/run-tests:
test-command: ./gradlew test<< parameters.flavour >>DebugUnitTest --continue
# Install Firebase tools needed for firebase emulator
- run:
name: Install firebase tools
command: |
curl -sL firebase.tools | bash
# Then start firebase emulator in the background
- run:
name: Start firebase emulator
command: |
firebase emulators:start
background: true
# Then start the emulator and run the Instrumentation tests!
- android/start-emulator-and-run-tests:
post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest
test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest
system-image: system-images;android-25;google_apis;x86
# store test reports
- store_artifacts:
path: app/build/reports/androidTests/connected
destination: reports
# Then publish the artifacts of the Firebase emulator logs!
- run:
name: save firebase emulator logs
command: |
mkdir -p tmp/firebase_logs
cp *.log tmp/firebase_logs
- store_artifacts:
path: tmp/firebase_logs
destination: logs
# Then publish the results of the Instrumentation tests!
- store_test_results:
path: app/build/outputs/androidTest-results/connected
# Assemble
assemble-and-release:
# Parameters used for determining
parameters: parameters:
flavour: flavour:
type: string type: string
default: "" default: "Driver"
executor: docker:
name: android/android-machine - image: cimg/android:2023.07-browsers
auth:
username: ${DOCKER_USERNAME}
password: ${DOCKER_PASSWORD}
steps: steps:
- run: - setup_repo
name: Setup variables for release - deploy_to_play_store:
command: | flavour: << parameters.flavour >>
echo "$RELEASE_KEYSTORE_BASE64" | base64 --decode > "android/app/release_keystore.jks"
echo "$GOOGLE_PLAY_KEY" > "android/playstore.json"
# And finally run the release build
- run:
name: Assemble release build
command: |
./gradlew assembleDriverRelease
# Invoke jobs via workflows # Invoke jobs via workflows
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows
workflows: workflows:
@@ -100,8 +181,14 @@ workflows:
build-release-driver: build-release-driver:
jobs: jobs:
- build-and-test: - build-and-test:
flavour: Driver context: appttude
- assemble-and-release: flavour: "Driver"
filters:
branches:
ignore:
- main_admin
- deploy-to-playstore:
context: appttude
flavour: "Driver" flavour: "Driver"
filters: filters:
branches: branches:
@@ -112,11 +199,18 @@ workflows:
build-release-admin: build-release-admin:
jobs: jobs:
- build-and-test: - build-and-test:
flavour: Admin context: appttude
- assemble-and-release: flavour: "Admin"
flavour: Admin
filters: filters:
branches: branches:
only: main_admin ignore:
- main_driver
- deploy-to-playstore:
context: appttude
flavour: "Admin"
filters:
branches:
only:
- main_admin
requires: requires:
- build-and-test - build-and-test

12
.gitignore vendored
View File

@@ -9,9 +9,21 @@
/.idea/navEditor.xml /.idea/navEditor.xml
/.idea/misc.xml /.idea/misc.xml
/.idea/assetWizardSettings.xml /.idea/assetWizardSettings.xml
/.idea/shelf/
.DS_Store .DS_Store
/build /build
/captures /captures
.externalNativeBuild .externalNativeBuild
*.log *.log
local local
# Circleci
/.circleci/local_config.yml
/.circleci/run_local.bash
# Gem/fastlane
/Gemfile.lock
/google-play-key.json
/app/google-services.json
/fastlane/report.xml
# Firebase emulator
database-debug.log
firebase-debug.log

6
.idea/compiler.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

View File

@@ -1,7 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="h_mal">
<words>
<w>viewmodel</w>
</words>
</dictionary>
</component>

20
.idea/gradle.xml generated
View File

@@ -1,20 +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="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Android Studio java home" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

48
.idea/misc.xml generated
View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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_PREVIEW" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

6
.idea/vcs.xml generated
View File

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

3
Gemfile Normal file
View File

@@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"

View File

@@ -9,7 +9,9 @@ plugins {
def relStorePassword = System.getenv("RELEASE_STORE_PASSWORD") def relStorePassword = System.getenv("RELEASE_STORE_PASSWORD")
def relKeyPassword = System.getenv("RELEASE_KEY_PASSWORD") def relKeyPassword = System.getenv("RELEASE_KEY_PASSWORD")
def relKeyAlias = System.getenv("RELEASE_KEY_ALIAS") def relKeyAlias = System.getenv("RELEASE_KEY_ALIAS")
def relStoreFile = System.getenv("RELEASE_KEYSTORE")
def keystorePath = System.getenv('PWD') + "/app/keystore.jks"
def keystore = file(keystorePath).exists() ? file(keystorePath) : null
android { android {
compileSdkVersion 31 compileSdkVersion 31
@@ -17,8 +19,8 @@ android {
applicationId "h_mal.appttude.com.driver" applicationId "h_mal.appttude.com.driver"
minSdkVersion 24 minSdkVersion 24
targetSdkVersion 31 targetSdkVersion 31
versionCode 6 versionCode 7
versionName "1.6" versionName "2.0.0"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
boolean state = project.rootProject.file('local.properties').canRead() boolean state = project.rootProject.file('local.properties').canRead()
@@ -41,7 +43,7 @@ android {
storePassword relStorePassword storePassword relStorePassword
keyPassword relKeyPassword keyPassword relKeyPassword
keyAlias relKeyAlias keyAlias relKeyAlias
// storeFile file(relStoreFile) storeFile keystore
} }
} }
@@ -66,22 +68,25 @@ android {
flavorDimensions "Default" flavorDimensions "Default"
productFlavors { productFlavors {
driver { driver {
versionCode 6 applicationId "h_mal.appttude.com.driver"
versionName "1.0.5" versionCode 7
versionName "2.0.0"
} }
admin { admin {
applicationIdSuffix ".admin" applicationId "h_mal.appttude.com.driver.admin"
versionCode 4 versionCode 4
versionName "0.0.5" versionName "0.0.5"
} }
} }
sourceSets { sourceSets {
driver { driver {
java.srcDirs += 'src/driver/java'
manifest { manifest {
srcFile 'src/driver/AndroidManifest.xml' srcFile 'src/driver/AndroidManifest.xml'
} }
} }
admin { admin {
java.srcDirs += 'src/admin/java'
manifest { manifest {
srcFile 'src/admin/AndroidManifest.xml' srcFile 'src/admin/AndroidManifest.xml'
} }
@@ -108,6 +113,7 @@ dependencies {
implementation 'androidx.viewpager:viewpager:1.0.0' implementation 'androidx.viewpager:viewpager:1.0.0'
implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.legacy:legacy-support-v4:1.0.0"
testImplementation "junit:junit:4.13.2" testImplementation "junit:junit:4.13.2"
implementation "androidx.preference:preference-ktx:1.2.0"
/ * Android Espresso */ / * Android Espresso */
def testJunitVersion = "1.1.5" def testJunitVersion = "1.1.5"
def testRunnerVersion = "1.5.2" def testRunnerVersion = "1.5.2"
@@ -117,6 +123,8 @@ dependencies {
androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espressoVersion" androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espressoVersion"
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion" implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
androidTestImplementation "androidx.test:runner:$testRunnerVersion" androidTestImplementation "androidx.test:runner:$testRunnerVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
/ * Google play services */ / * Google play services */
implementation "com.google.android.gms:play-services-auth:20.4.1" implementation "com.google.android.gms:play-services-auth:20.4.1"
/ * Google firebase */ / * Google firebase */
@@ -128,6 +136,7 @@ dependencies {
implementation "com.google.firebase:firebase-auth:$firebaseAuth" implementation "com.google.firebase:firebase-auth:$firebaseAuth"
implementation "com.google.firebase:firebase-storage:$firebaseStorage" implementation "com.google.firebase:firebase-storage:$firebaseStorage"
implementation "com.google.firebase:firebase-database:$firebaseDatabase" implementation "com.google.firebase:firebase-database:$firebaseDatabase"
implementation 'com.firebaseui:firebase-ui-database:8.0.2'
/ * Photoviewer */ / * Photoviewer */
implementation "com.github.chrisbanes:PhotoView:2.1.0" implementation "com.github.chrisbanes:PhotoView:2.1.0"
/ * Picasso photo loader */ / * Picasso photo loader */
@@ -151,4 +160,10 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.10.0' implementation 'com.squareup.okhttp3:okhttp:4.10.0'
/ * Kotlin Reflect */ / * Kotlin Reflect */
implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.10" implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.10"
/ * Retrofit2 */
def retrofit_version = "2.9.0"
androidTestImplementation "com.squareup.retrofit2:retrofit:$retrofit_version"
androidTestImplementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
/ * screenshot library */
androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
} }

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- To auto-complete the email text field in the login form with the user's emails -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round">
</application>
</manifest>

View File

@@ -0,0 +1,68 @@
package h_mal.appttude.com.driver.application
import android.content.res.Resources
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import h_mal.appttude.com.driver.data.FirebaseAuthSource
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.prefs.PreferenceProvider
import h_mal.appttude.com.driver.viewmodels.*
class ApplicationViewModelFactory(
private val auth: FirebaseAuthSource,
private val database: FirebaseDatabaseSource,
private val storage: FirebaseStorageSource,
private val preferences: PreferenceProvider,
private val resources: Resources
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
with(modelClass) {
return when {
isAssignableFrom(UserViewModel::class.java) -> UserViewModel(auth)
isAssignableFrom(MainViewModel::class.java) -> MainViewModel(auth, database)
isAssignableFrom(UpdateUserViewModel::class.java) -> UpdateUserViewModel(
auth,
storage
)
isAssignableFrom(RoleViewModel::class.java) -> RoleViewModel(
auth,
database,
storage
)
isAssignableFrom(DriverLicenseViewModel::class.java) -> DriverLicenseViewModel(
database
)
isAssignableFrom(DriverProfileViewModel::class.java) -> DriverProfileViewModel(
database
)
isAssignableFrom(PrivateHireLicenseViewModel::class.java) -> PrivateHireLicenseViewModel(
database
)
isAssignableFrom(VehicleProfileViewModel::class.java) -> VehicleProfileViewModel(
database
)
isAssignableFrom(InsuranceViewModel::class.java) -> InsuranceViewModel(database)
isAssignableFrom(MotViewModel::class.java) -> MotViewModel(database)
isAssignableFrom(LogbookViewModel::class.java) -> LogbookViewModel(database)
isAssignableFrom(PrivateHireVehicleViewModel::class.java) -> PrivateHireVehicleViewModel(
database
)
isAssignableFrom(DriverOverviewViewModel::class.java) -> DriverOverviewViewModel(
auth,
database
)
isAssignableFrom(SuperUserViewModel::class.java) -> SuperUserViewModel(
database,
preferences
)
isAssignableFrom(ApproverViewModel::class.java) -> ApproverViewModel(resources , database)
else -> throw IllegalArgumentException("Unknown ViewModel class")
} as T
}
}
}

View File

@@ -0,0 +1,23 @@
package h_mal.appttude.com.driver.application
import h_mal.appttude.com.driver.data.prefs.PreferenceProvider
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.provider
import org.kodein.di.generic.singleton
class DriverApplication : BaseApplication() {
override val flavourModule = super.flavourModule.copy {
bind() from singleton { PreferenceProvider(this@DriverApplication) }
bind() from provider {
ApplicationViewModelFactory(
instance(),
instance(),
instance(),
instance(),
instance()
)
}
}
}

View File

@@ -0,0 +1,40 @@
package h_mal.appttude.com.driver.base
import com.google.firebase.database.DatabaseReference
import h_mal.appttude.com.driver.data.FirebaseCompletion
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.GenericsHelper.getGenericClassAt
import h_mal.appttude.com.driver.utils.getDataFromDatabaseRef
import h_mal.appttude.com.driver.utils.isNotNull
abstract class DataViewerBaseViewModel<T : Any> : BaseViewModel() {
var uid: String? = null
abstract fun getDatabaseRef(uid: String): DatabaseReference
fun initViewModel(uid: String) {
this.uid = uid
retrieveData(uid)
}
fun fetchData() {
@Suppress("IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION")
uid.isNotNull {
retrieveData(it)
return
}
onError("Failed to retrieve data for user")
}
private fun retrieveData(uid: String) {
val clazz = getGenericClassAt<T>(0)
io {
doTryOperation("Failed to retrieve ${clazz.simpleName}") {
val data = getDatabaseRef(uid).getDataFromDatabaseRef(clazz.java)
onSuccess(data ?: FirebaseCompletion.Default)
}
}
}
}

View File

@@ -0,0 +1,55 @@
package h_mal.appttude.com.driver.base
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.EditText
import androidx.core.view.children
import androidx.viewbinding.ViewBinding
import h_mal.appttude.com.driver.data.USER_CONST
import h_mal.appttude.com.driver.utils.GenericsHelper.getGenericClassAt
import h_mal.appttude.com.driver.utils.hide
import h_mal.appttude.com.driver.utils.isTrue
open class DataViewerFragment<V : DataViewerBaseViewModel<T>, VB : ViewBinding, T : Any> :
BaseFragment<V, VB>() {
override fun setupView(binding: VB) {
super.setupView(binding)
(binding.root as ViewGroup).children.forEach { disableViews(it) }
}
private fun disableViews(view: View) {
if (view is EditText)
view.isFocusable = false
if (view is CheckBox)
view.isFocusable = false
else if (view is ViewGroup)
view.children.forEach { disableViews(it) }
}
override fun onStart() {
super.onStart()
requireArguments().getString(USER_CONST)?.let {
viewModel.initViewModel(it)
}
}
@Suppress("UNCHECKED_CAST")
override fun onSuccess(data: Any?) {
super.onSuccess(data)
data?.let { (data::class == getGenericClassAt<T>(2)) }?.isTrue {
setFields(data as T)
}
}
open fun setFields(data: T) {}
fun viewsToHide(vararg view: View) {
view.forEach { it.hide() }
}
}

View File

@@ -0,0 +1,19 @@
package h_mal.appttude.com.driver.model
import h_mal.appttude.com.driver.R
enum class ApprovalStatus(val stringId: Int, val drawableId: Int, val score: Int) {
NOT_SUBMITTED(R.string.not_submitted, R.drawable.denied, 0),
DENIED(R.string.denied, R.drawable.denied, 1),
PENDING_APPROVAL(R.string.pending, R.drawable.pending, 2),
APPROVED(R.string.approved, R.drawable.approved, 3);
companion object {
infix fun getByScore(value: Int): ApprovalStatus? =
ApprovalStatus.values().firstOrNull { it.score == value }
infix fun getByStringId(value: Int): ApprovalStatus? =
ApprovalStatus.values().firstOrNull { it.stringId == value }
infix fun getByDrawableId(value: Int): ApprovalStatus? =
ApprovalStatus.values().firstOrNull { it.drawableId == value }
}
}

View File

@@ -0,0 +1,27 @@
package h_mal.appttude.com.driver.model
import h_mal.appttude.com.driver.R
enum class DatabaseStatus(val drawable: Int, val header: Int, val subtext: Int) {
NO_CONNECTION(R.drawable.baseline_inbox_24, R.string.no_connection, R.string.no_connection_subtext),
NO_PERMISSION(
R.drawable.baseline_inbox_24,
R.string.no_permission,
R.string.no_permission_subtext
),
CANNOT_RETRIEVE(
R.drawable.baseline_inbox_24,
R.string.cannot_retrieve,
R.string.cannot_retrieve_subtext
),
NO_AUTHORIZATION(
R.drawable.baseline_inbox_24,
R.string.no_authorization,
R.string.no_authorization_subtext
),
EMPTY_RESULTS(
R.drawable.baseline_inbox_24,
R.string.no_drivers_to_show,
R.string.no_drivers_subtext
)
}

View File

@@ -0,0 +1,17 @@
package h_mal.appttude.com.driver.model
enum class SortOption(val key: String, val label: String) {
NAME("driver_profile/driver_details/forenames","Driver Name"),
NUMBER("driver_number", "Driver Number");
// APPROVAL("forenames")
companion object {
fun getSortOptionByLabel(label: String?): SortOption? {
return values().firstOrNull { i -> i.label == label }
}
fun getPositionByLabel(label: String?): Int? {
val sortOption = getSortOptionByLabel(label) ?: return null
return values().indexOf(sortOption)
}
}
}

View File

@@ -1,34 +1,13 @@
package h_mal.appttude.com.driver.admin.objects package h_mal.appttude.com.driver.objects
class ApprovalsObject { data class ApprovalsObject (
var driver_details_approval: Int = 0 var driver_details_approval: Int = 0,
var driver_license_approval: Int = 0 var driver_license_approval: Int = 0,
var private_hire_approval: Int = 0 var private_hire_approval: Int = 0,
var vehicle_details_approval: Int = 0 var vehicle_details_approval: Int = 0,
var mot_details_approval: Int = 0 var mot_details_approval: Int = 0,
var insurance_details_approval: Int = 0 var insurance_details_approval: Int = 0,
var log_book_approval: Int = 0 var log_book_approval: Int = 0,
var ph_car_approval: Int = 0 var ph_car_approval: Int = 0,
)
constructor()
constructor(
driver_details_approval: Int,
driver_license_approval: Int,
private_hire_approval: Int,
vehicle_details_approval: Int,
mot_details_approval: Int,
insurance_details_approval: Int,
log_book_approval: Int,
private_hire_vehicle_approval: Int
) {
this.driver_details_approval = driver_details_approval
this.driver_license_approval = driver_license_approval
this.private_hire_approval = private_hire_approval
this.vehicle_details_approval = vehicle_details_approval
this.mot_details_approval = mot_details_approval
this.insurance_details_approval = insurance_details_approval
this.log_book_approval = log_book_approval
this.ph_car_approval = private_hire_vehicle_approval
}
}

View File

@@ -1,33 +1,20 @@
package h_mal.appttude.com.driver.admin.objects package h_mal.appttude.com.driver.objects
import h_mal.appttude.com.driver.model.* import h_mal.appttude.com.driver.model.DriversLicense
import java.util.* import h_mal.appttude.com.driver.model.Insurance
import h_mal.appttude.com.driver.model.Logbook
import h_mal.appttude.com.driver.model.Mot
import h_mal.appttude.com.driver.model.PrivateHireLicense
import h_mal.appttude.com.driver.model.PrivateHireVehicle
import h_mal.appttude.com.driver.model.VehicleProfile
class ArchiveObject {
var driver_license: HashMap<String, DriversLicense>? = null
var private_hire: HashMap<String, PrivateHireLicense>? = null
var vehicle_details: HashMap<String, VehicleProfile>? = null
var insurance_details: HashMap<String, Insurance>? = null
var mot_details: HashMap<String, Mot>? = null
var log_book: HashMap<String, Logbook>? = null
var ph_car: HashMap<String, PrivateHireVehicle>? = null
constructor() data class ArchiveObject(
constructor( var driver_license: HashMap<String, DriversLicense>? = HashMap(),
driver_license: HashMap<String, DriversLicense>?, var private_hire: HashMap<String, PrivateHireLicense>? = HashMap(),
private_hire: HashMap<String, PrivateHireLicense>?, var vehicle_details: HashMap<String, VehicleProfile>? = HashMap(),
vehicle_details: HashMap<String, VehicleProfile>?, var insurance_details: HashMap<String, Insurance>? = HashMap(),
insurance_details: HashMap<String, Insurance>?, var mot_details: HashMap<String, Mot>? = HashMap(),
mot_details: HashMap<String, Mot>?, var log_book: HashMap<String, Logbook>? = HashMap(),
log_book: HashMap<String, Logbook>?, var ph_car: HashMap<String, PrivateHireVehicle>? = HashMap(),
private_hire_vehicle: HashMap<String, PrivateHireVehicle>? )
) {
this.driver_license = driver_license
this.private_hire = private_hire
this.vehicle_details = vehicle_details
this.insurance_details = insurance_details
this.mot_details = mot_details
this.log_book = log_book
this.ph_car = private_hire_vehicle
}
}

View File

@@ -1,15 +1,10 @@
package h_mal.appttude.com.driver.admin.objects package h_mal.appttude.com.driver.objects
import com.google.firebase.database.IgnoreExtraProperties
class UserObject { @IgnoreExtraProperties
var profileName: String? = null data class UserObject (
var profileEmail: String? = null var profileName: String? = "",
var profilePicString: String? = null var profileEmail: String? = "",
var profilePicString: String? = "",
constructor() )
constructor(profileName: String?, profileEmail: String?, profilePicString: String?) {
this.profileName = profileName
this.profileEmail = profileEmail
this.profilePicString = profilePicString
}
}

View File

@@ -1,35 +1,15 @@
package h_mal.appttude.com.driver.admin.objects package h_mal.appttude.com.driver.objects
import h_mal.appttude.com.driver.admin.objects.wholeObject.DriverProfile import h_mal.appttude.com.driver.objects.wholeObject.DriverProfile
import h_mal.appttude.com.driver.admin.objects.wholeObject.VehicleProfile import h_mal.appttude.com.driver.objects.wholeObject.VehicleProfile
class WholeDriverObject { data class WholeDriverObject(
var driver_profile: DriverProfile? = null var driver_profile: DriverProfile? = DriverProfile(),
var role: String? = null var role: String? = "",
var archive: ArchiveObject? = null var archive: ArchiveObject? = ArchiveObject(),
var user_details: UserObject? = null var user_details: UserObject? = UserObject(),
var vehicle_profile: VehicleProfile? = null var vehicle_profile: VehicleProfile? = VehicleProfile(),
var approvalsObject: ApprovalsObject? = null var approvalsObject: ApprovalsObject? = ApprovalsObject(),
var driver_number: String? = null var driver_number: String? = "",
)
constructor(
driver_profile: DriverProfile?,
role: String?,
archive: ArchiveObject?,
user_details: UserObject?,
vehicle_profile: VehicleProfile?,
approvalsObject: ApprovalsObject?,
driver_number: String?
) {
this.driver_profile = driver_profile
this.role = role
this.archive = archive
this.user_details = user_details
this.vehicle_profile = vehicle_profile
this.approvalsObject = approvalsObject
this.driver_number = driver_number
}
constructor()
}

View File

@@ -1,24 +1,12 @@
package h_mal.appttude.com.driver.admin.objects.wholeObject package h_mal.appttude.com.driver.objects.wholeObject
import h_mal.appttude.com.driver.model.DriverProfile import h_mal.appttude.com.driver.model.DriverProfile
import h_mal.appttude.com.driver.model.DriversLicense import h_mal.appttude.com.driver.model.DriversLicense
import h_mal.appttude.com.driver.model.PrivateHireLicense import h_mal.appttude.com.driver.model.PrivateHireLicense
class DriverProfile { data class DriverProfile(
var driver_profile: DriverProfile? = null var driver_profile: DriverProfile? = DriverProfile(),
var driver_license: DriversLicense? = null var driver_license: DriversLicense? = DriversLicense(),
var private_hire: PrivateHireLicense? = null var private_hire: PrivateHireLicense? = PrivateHireLicense(),
)
constructor(
driver_profile: DriverProfile?,
driver_license: DriversLicense?,
private_hire: PrivateHireLicense?
) {
this.driver_profile = driver_profile
this.driver_license = driver_license
this.private_hire = private_hire
}
constructor()
}

View File

@@ -1,43 +0,0 @@
package h_mal.appttude.com.driver.admin.objects.wholeObject
import android.os.Parcel
import android.os.Parcelable
import h_mal.appttude.com.driver.admin.objects.WholeDriverObject
class MappedObject : Parcelable {
var userId: String? = null
var wholeDriverObject: WholeDriverObject? = null
constructor(userId: String?, wholeDriverObject: WholeDriverObject?) {
this.userId = userId
this.wholeDriverObject = wholeDriverObject
}
constructor()
protected constructor(`in`: Parcel) {
userId = `in`.readString()
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(userId)
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<MappedObject?> =
object : Parcelable.Creator<MappedObject?> {
override fun createFromParcel(`in`: Parcel): MappedObject? {
return MappedObject(`in`)
}
override fun newArray(size: Int): Array<MappedObject?> {
return arrayOfNulls(size)
}
}
}
}

View File

@@ -1,31 +1,15 @@
package h_mal.appttude.com.driver.admin.objects.wholeObject package h_mal.appttude.com.driver.objects.wholeObject
import h_mal.appttude.com.driver.model.Insurance import h_mal.appttude.com.driver.model.Insurance
import h_mal.appttude.com.driver.model.Logbook import h_mal.appttude.com.driver.model.Logbook
import h_mal.appttude.com.driver.model.PrivateHireVehicle
import h_mal.appttude.com.driver.model.Mot import h_mal.appttude.com.driver.model.Mot
import h_mal.appttude.com.driver.model.PrivateHireVehicle
import h_mal.appttude.com.driver.model.VehicleProfile import h_mal.appttude.com.driver.model.VehicleProfile
data class VehicleProfile (
class VehicleProfile { var insurance_details: Insurance? = Insurance(),
var insurance_details: Insurance? = null var log_book: Logbook? = Logbook(),
var log_book: Logbook? = null var mot_details: Mot? = Mot(),
var mot_details: Mot? = null var vehicle_details: VehicleProfile? = VehicleProfile(),
var vehicle_details: VehicleProfile? = null var privateHireVehicle: PrivateHireVehicle? = PrivateHireVehicle()
var privateHireVehicle: PrivateHireVehicle? = null )
constructor()
constructor(
insurance_details: Insurance?,
log_book: Logbook?,
mot_details: Mot?,
vehicle_details: VehicleProfile?,
private_hire_vehicle: PrivateHireVehicle?
) {
this.insurance_details = insurance_details
this.log_book = log_book
this.mot_details = mot_details
this.vehicle_details = vehicle_details
this.privateHireVehicle = private_hire_vehicle
}
}

View File

@@ -1,188 +1,57 @@
package h_mal.appttude.com.driver.ui package h_mal.appttude.com.driver.ui
import android.app.Activity import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import h_mal.appttude.com.driver.admin.objects.wholeObject.MappedObject import h_mal.appttude.com.driver.databinding.ApprovalListItemBinding
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.model.ApprovalStatus
import h_mal.appttude.com.driver.utils.hide
import java.io.IOException
class ApprovalListAdapter( class ApprovalListAdapter(
private val activity: Activity, private val context: Context,
objects: Array<MappedObject?> private val data: List<Pair<String, ApprovalStatus>>,
) : ArrayAdapter<MappedObject?>(activity, 0, objects) { private val callback: (String) -> Unit
) : ArrayAdapter<Pair<String, ApprovalStatus>>(context, 0, data) {
var mappedObject: MappedObject? = objects[0] override fun getCount(): Int = data.size
var names: Array<String> = arrayOf(
"Driver Profile",
"Driver License",
"Private Hire",
"Vehicle Profile",
"Insurance",
"MOT",
"Logbook",
"P/H Vehicle"
)
var approvalCode: Int = 0
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var listItemView: View? = convertView var listItemView: View? = convertView
val binding: ApprovalListItemBinding
if (listItemView == null) { if (listItemView == null) {
listItemView = LayoutInflater.from(activity).inflate( // Inflate view binding into listview cell
R.layout.approval_list_grid_item, parent, false binding = ApprovalListItemBinding.inflate(LayoutInflater.from(context), parent, false)
) listItemView = binding.root
listItemView.setTag(listItemView.id, binding)
} else {
// cell exists so recycling view
binding = listItemView.getTag(listItemView.id) as ApprovalListItemBinding
} }
// approvalCode = getApprovalStatusCode(position)
// val textView: TextView = listItemView!!.findViewById(R.id.approval_text) val key: String = getItem(position)?.first ?: throw IOException("No document name provided")
// textView.text = names.get(position) val approvalStatus: ApprovalStatus? = getItem(position)?.second
// val imageView: ImageView = listItemView.findViewById(R.id.approval_iv)
// imageView.setImageResource( binding.approvalText.text = key
// MainActivity.approvalsClass!!.setImageResource( approvalStatus?.let { item ->
// approvalCode item.score.takeIf { it != 0 }?.let {
// ) binding.root.setOnClickListener { callback.invoke(key) }
// ) }
// imageView.setOnClickListener { binding.approvalIv.setImageResource(item.drawableId)
// SetApprovalDialog( binding.approvalStatus.text = context.getString(item.stringId)
// approvalCode, }
// activity, // hide divider for first cell
// mappedObject.userId, if (position == 0) binding.divider.hide()
// position,
// imageView return (listItemView)
// )
// }
// val archiveImage: ImageView = listItemView.findViewById(R.id.archive_icon)
// mappedObject.wholeDriverObject?.archive?.let {
//
//
// archiveImage.visibility = getArchive(
// position,
// it
// )
// archiveImage.setOnClickListener {
// var s: String? = null
// when (position) {
// 1 -> s = FirebaseClass.DRIVERS_LICENSE_FIREBASE
// 2 -> s = FirebaseClass.PRIVATE_HIRE_FIREBASE
// 3 -> s = FirebaseClass.VEHICLE_DETAILS_FIREBASE
// 4 -> s = FirebaseClass.INSURANCE_FIREBASE
// 5 -> s = FirebaseClass.MOT_FIREBASE
// 6 -> s = FirebaseClass.LOG_BOOK_FIREBASE
// 7 -> s = FirebaseClass.PRIVATE_HIRE_VEHICLE_LICENSE
// }
//// executeFragment(ArchiveFragment(), mappedObject.userId, s)
// }
// }
// listItemView.setOnClickListener(View.OnClickListener { getFragment(position) })
// listItemView.minimumHeight = parent.height / 4
// listItemView.setPadding(
// convertDpToPixel(9f, context).toInt(),
// convertDpToPixel(9f, context).toInt(),
// convertDpToPixel(9f, context).toInt(),
// convertDpToPixel(9f, context).toInt()
// )
return (listItemView)!!
} }
// override fun getCount(): Int { fun updateAdapter(date: List<Pair<String, ApprovalStatus>>) {
// return 8 clear()
// } addAll(date)
// }
// private fun getArchive(i: Int, archiveObject: ArchiveObject?): Int {
// var o: Any? = null
// val visible: Int
// when (i) {
// 0 -> { }
// 1 -> o = archiveObject!!.driver_license
// 2 -> o = archiveObject!!.private_hire
// 3 -> o = archiveObject!!.vehicle_details
// 4 -> o = archiveObject!!.insurance_details
// 5 -> o = archiveObject!!.mot_details
// 6 -> o = archiveObject!!.log_book
// 7 -> o = archiveObject!!.ph_car
// }
// if (o != null) {
// visible = View.VISIBLE
// } else {
// visible = View.GONE
// }
// return visible
// }
//
// private fun getFragment(i: Int) {
// lateinit var f: Fragment
// val driverProfile by lazy { mappedObject.wholeDriverObject?.driver_profile }
// val vehicleProfile by lazy { mappedObject.wholeDriverObject?.vehicle_profile }
// val o: Any? = when (i) {
// 0 -> {
// f = DriverProfileFragment()
// driverProfile?.driver_profile
// }
// 1 -> {
// f = DriverLicenseFragment()
// driverProfile?.driver_license
// }
// 2 -> {
// f = PrivateHireLicenseFragment()
// driverProfile?.private_hire
// }
// 3 -> {
// f = VehicleProfileFragment()
// vehicleProfile?.vehicle_details
// }
// 4 -> {
// f = InsuranceFragment()
// vehicleProfile?.insurance_details
// }
// 5 -> {
// f = MotFragment()
// vehicleProfile?.insurance_details
// }
// 6 -> {
// f = LogbookFragment()
// vehicleProfile?.log_book
// }
// 7 -> {
// f = PrivateHireVehicleFragment()
// vehicleProfile?.privateHireVehicleObject
// }
// else -> null
// }
// if (o == null) {
//// executeFragment(f, mappedObject.userId)
// } else {
// MainActivity.archiveClass.openDialogArchive(
// context, o, mappedObject.userId, f
// )
// }
// }
//
// private fun getApprovalStatusCode(i: Int): Int {
// val statusCode = mappedObject.wholeDriverObject?.approvalsObject?.let{
// when (i) {
// 0 -> it.driver_details_approval
// 1 -> it.driver_license_approval
// 2 -> it.private_hire_approval
// 3 -> it.vehicle_details_approval
// 4 -> it.insurance_details_approval
// 5 -> it.mot_details_approval
// 6 -> it.log_book_approval
// 7 -> it.ph_car_approval
// else -> FirebaseClass.NO_DATE_PRESENT
// }
// }
// return statusCode ?: FirebaseClass.NO_DATE_PRESENT
// }
//
// companion object {
// fun convertDpToPixel(dp: Float, context: Context): Float {
// return dp * (context.resources
// .displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
// }
// }
} }

View File

@@ -0,0 +1,39 @@
package h_mal.appttude.com.driver.ui
import h_mal.appttude.com.driver.base.BaseFragment
import h_mal.appttude.com.driver.databinding.FragmentApproverBinding
import h_mal.appttude.com.driver.model.ApprovalStatus
import h_mal.appttude.com.driver.viewmodels.ApproverViewModel
class ApproverFragment : BaseFragment<ApproverViewModel, FragmentApproverBinding>() {
override fun setupView(binding: FragmentApproverBinding) = binding.run {
super.setupView(binding)
val args = requireArguments()
viewModel.init(args)
// Retrieve fragment name argument saved from previous fragment
val fragmentClass = viewModel.getFragmentClass()
childFragmentManager.beginTransaction()
.replace(container.id, fragmentClass, args, null)
.commitNow()
approve.setOnClickListener { viewModel.approveDocument() }
decline.setOnClickListener { viewModel.declineDocument() }
}
override fun onSuccess(data: Any?) {
super.onSuccess(data)
when (data) {
ApprovalStatus.APPROVED -> displaySnackBar("approved")
ApprovalStatus.DENIED -> displaySnackBar("declined")
}
}
private fun displaySnackBar(status: String) {
showSnackBar("Document has been $status")
}
}

View File

@@ -0,0 +1,54 @@
package h_mal.appttude.com.driver.ui
import android.widget.ListView
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.base.BaseFragment
import h_mal.appttude.com.driver.data.USER_CONST
import h_mal.appttude.com.driver.databinding.FragmentUserMainBinding
import h_mal.appttude.com.driver.model.ApprovalStatus
import h_mal.appttude.com.driver.utils.FRAGMENT
import h_mal.appttude.com.driver.utils.navigateTo
import h_mal.appttude.com.driver.utils.toBundle
import h_mal.appttude.com.driver.viewmodels.DriverOverviewViewModel
import java.io.IOException
class DriverOverviewFragment : BaseFragment<DriverOverviewViewModel, FragmentUserMainBinding>() {
private lateinit var listView: ListView
private lateinit var driverId: String
override fun setupView(binding: FragmentUserMainBinding) {
listView = binding.approvalsList
loadList()
}
override fun onResume() {
super.onResume()
loadList()
}
override fun onSuccess(data: Any?) {
super.onSuccess(data)
@Suppress("UNCHECKED_CAST")
if (data is List<*>) {
val listData = data as List<Pair<String, ApprovalStatus>>
if (listView.adapter == null) {
listView.adapter = ApprovalListAdapter(requireContext(), listData) {
this.view?.navigateTo(
R.id.to_approverFragment,
driverId.toBundle(USER_CONST).apply { putString(FRAGMENT, it) })
}
listView.isScrollContainer = false
} else {
(listView.adapter as ApprovalListAdapter).updateAdapter(listData)
}
}
}
private fun loadList() {
driverId = requireArguments().getString(USER_CONST)
?: throw IOException("No user ID has been passed")
viewModel.loadDriverApprovals(driverId)
}
}

View File

@@ -1,107 +0,0 @@
package h_mal.appttude.com.driver.ui
import android.app.AlertDialog
import android.content.Context
import android.view.View
import androidx.cardview.widget.CardView
class DriverStatusClass : View.OnClickListener {
var userId: String? = null
var cardView: CardView? = null
var context: Context? = null
var currentSelection: Boolean = false
override fun onClick(v: View) {
val choices: Array<String> = arrayOf("Active", "Inactive")
val alertDialog: AlertDialog.Builder = AlertDialog.Builder(context)
var selection: Int = -1
if (currentSelection) {
selection = 0
} else if (!currentSelection) {
selection = 1
}
alertDialog.setSingleChoiceItems(
choices,
selection
) { dialog, which -> }
alertDialog.create().show()
}
private fun SetStatus(status: Boolean) {
// MainActivity.mDatabase!!.child(FirebaseClass.USER_FIREBASE)
// .child((userId)!!).child(FirebaseClass.DRIVER_STATUS).setValue(status)
// .addOnCompleteListener { task ->
// if (task.isSuccessful) {
// cardView!!.setBackgroundColor(setStatusColour(status))
// } else {
// }
// }
}
private fun setStatusColour(b: Boolean): Int {
if (b) {
return android.R.color.holo_green_dark
} else {
return android.R.color.holo_red_dark
}
} // public int getOverApprovalStatusCode(WholeDriverObject wholeDriverObject){
//
// if (wholeDriverObject.approvalsObject != null){
// ApprovalsObject approvalsObject = wholeDriverObject.approvalsObject;
//
// int[] ints = new int[]{approvalsObject.getDriver_details_approval(),
// approvalsObject.driver_license_approval,
// approvalsObject.private_hire_approval,
// approvalsObject.vehicle_details_approval,
// approvalsObject.insurance_details_approval,
// approvalsObject.mot_details_approval,
// approvalsObject.log_book_approval,
// approvalsObject.ph_car_approval};
//
//
// return setImageResource(mode(ints));
// }
//
// return setImageResource(NO_DATE_PRESENT);
// }
//
// public void setStatusCode(String userId,String approvalNameString,int status){
//
// if (!approvalNameString.equals("")) {
// mDatabase.child(USER_FIREBASE).child(userId).child(USER_APPROVALS).child(approvalNameString)
// .setValue(status).addOnCompleteListener(new OnCompleteListener<Void>() {
// @Override
// public void onComplete(@NonNull Task<Void> task) {
// if (task.isSuccessful()) {
//
// } else {
//
// }
// }
// });
// }
//
//
// }
//
// public int setImageResource(int statusCode){
// int imageResource;
//
// switch (statusCode){
// case APPROVAL_PENDING:
// imageResource = R.drawable.pending;
// break;
// case APPROVAL_DENIED:
// imageResource = R.drawable.denied;
// break;
// case APPROVED:
// imageResource = R.drawable.approved;
// break;
// default:
// imageResource = R.drawable.zero;
// break;
// }
//
// return imageResource;
// }
}

View File

@@ -1,153 +1,208 @@
package h_mal.appttude.com.driver.ui package h_mal.appttude.com.driver.ui
import android.app.AlertDialog import android.app.AlertDialog
import android.content.DialogInterface
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.* import android.view.*
import androidx.fragment.app.Fragment import android.widget.EditText
import androidx.recyclerview.widget.LinearLayoutManager import android.widget.LinearLayout
import androidx.recyclerview.widget.RecyclerView import androidx.core.view.MenuProvider
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.Lifecycle
import com.firebase.ui.common.ChangeEventType
import com.firebase.ui.database.FirebaseRecyclerAdapter
import com.firebase.ui.database.FirebaseRecyclerOptions
import com.google.firebase.database.DataSnapshot import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ValueEventListener
import h_mal.appttude.com.driver.admin.objects.WholeDriverObject
import h_mal.appttude.com.driver.admin.objects.wholeObject.MappedObject
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
import java.io.IOException import h_mal.appttude.com.driver.base.BaseFirebaseAdapter
import h_mal.appttude.com.driver.base.BaseFragment
import h_mal.appttude.com.driver.base.CustomViewHolder
import h_mal.appttude.com.driver.data.USER_CONST
import h_mal.appttude.com.driver.databinding.FragmentHomeSuperUserBinding
import h_mal.appttude.com.driver.databinding.ListItemLayoutBinding
import h_mal.appttude.com.driver.model.DatabaseStatus
import h_mal.appttude.com.driver.model.DatabaseStatus.*
import h_mal.appttude.com.driver.model.SortOption
import h_mal.appttude.com.driver.objects.UserObject
import h_mal.appttude.com.driver.objects.WholeDriverObject
import h_mal.appttude.com.driver.utils.*
import h_mal.appttude.com.driver.viewmodels.SuperUserViewModel
import java.util.* import java.util.*
class HomeSuperUserFragment : Fragment() { class HomeSuperUserFragment : BaseFragment<SuperUserViewModel, FragmentHomeSuperUserBinding>(),
var users: DatabaseReference? = null MenuProvider {
var mappedObjectList: MutableList<MappedObject>? = null private lateinit var adapter: FirebaseRecyclerAdapter<WholeDriverObject, CustomViewHolder<ListItemLayoutBinding>>
private var sharedPreferences: SharedPreferences? = null
private var sortOrder: Int = 0 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
private val sortDesc: Boolean = false super.onViewCreated(view, savedInstanceState)
private var recyclerViewAdapter: RecyclerViewAdapter? = null requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
override fun onCreate(savedInstanceState: Bundle?) { viewModel.retrieveDefaultFirebaseOptions()
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
mappedObjectList = ArrayList()
users!!.addValueEventListener(valueEventListener)
sharedPreferences = requireActivity().getSharedPreferences("PREFS", 0)
} }
override fun onCreateView( override fun onSuccess(data: Any?) {
inflater: LayoutInflater, container: ViewGroup?, super.onSuccess(data)
savedInstanceState: Bundle? when (data) {
): View? { is FirebaseRecyclerOptions<*> -> setAdapterToRecyclerView(data)
// Inflate the layout for this fragment }
val view: View = inflater.inflate(R.layout.fragment_home_super_user, container, false) }
private fun setNonView(status: DatabaseStatus) {
view.findViewById<RecyclerView>(R.id.recycler_view).apply { applyBinding {
layoutManager = LinearLayoutManager(context) emptyView.run {
recyclerViewAdapter = RecyclerViewAdapter(context, mappedObjectList) root.setOnClickListener(null)
adapter = recyclerViewAdapter root.visibility = View.VISIBLE
icon.setImageResource(status.drawable)
header.setText(status.header)
subtext.setText(status.subtext)
}
} }
return view
} }
var valueEventListener: ValueEventListener = object : ValueEventListener { @Suppress("UNCHECKED_CAST")
override fun onDataChange(snapshot: DataSnapshot) { private fun setAdapterToRecyclerView(options: FirebaseRecyclerOptions<*>) {
mappedObjectList!!.clear() applyBinding {
Log.i("Count ", "" + snapshot.childrenCount) progressCircular.show()
for (postSnapshot: DataSnapshot in snapshot.children) { if (recyclerView.adapter == null) {
if ((postSnapshot.child("role").value.toString() == "driver")) { // create an adapter for the first time
mappedObjectList!!.add( adapter = createAdapter(options = options as FirebaseRecyclerOptions<WholeDriverObject>)
MappedObject( recyclerView.adapter = adapter
postSnapshot.key, postSnapshot.getValue( recyclerView.setHasFixedSize(true)
WholeDriverObject::class.java adapter.startListening()
) } else {
adapter.updateOptions(options as FirebaseRecyclerOptions<WholeDriverObject>)
}
}
}
override fun onStart() {
super.onStart()
applyBinding {
if (recyclerView.adapter != null) {
adapter.startListening()
}
}
}
override fun onStop() {
super.onStop()
adapter.stopListening()
}
private fun createAdapter(options: FirebaseRecyclerOptions<WholeDriverObject>): BaseFirebaseAdapter<WholeDriverObject, ListItemLayoutBinding> {
return object :
BaseFirebaseAdapter<WholeDriverObject, ListItemLayoutBinding>(options, layoutInflater) {
override fun onBindViewHolder(
holder: CustomViewHolder<ListItemLayoutBinding>,
position: Int,
model: WholeDriverObject
) {
val userDetails: UserObject? = model.user_details
holder.viewBinding.apply {
driverPic.setGlideImage(userDetails?.profilePicString)
usernameText.text = userDetails?.profileName
emailaddressText.text = userDetails?.profileEmail
driverNo.run {
val number =
if (model.driver_number.isNullOrBlank()) "#N/A" else model.driver_number
text = number
setOnClickListener {
getKeyAtPosition(position)?.let { showChangeNumberDialog(number!!, it) }
}
}
root.setOnClickListener {
it.navigateTo(
R.id.action_homeAdminFragment_to_userMainFragment,
snapshots.getSnapshot(position).key?.toBundle(USER_CONST)
) )
) }
} }
} }
sortDate(sortOrder, sortDesc)
} override fun onDataChanged() {
super.onDataChanged()
override fun onCancelled(databaseError: DatabaseError) { applyBinding {
// If there are no driver data, show a view that informs the admin.
} emptyView.root.visibility = if (itemCount == 0) View.VISIBLE else View.GONE
} progressCircular.hide()
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_calls_fragment, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.archive) {
val grpname: Array<String> = arrayOf("Driver Name", "Driver Number", "Approval")
sortOrder = sharedPreferences!!.getInt(SORT, 0)
val checkedItem: Int = sortOrder
var compareInt = 0
val click = DialogInterface.OnClickListener { dialog, _ ->
sortDate(compareInt, false)
dialog.dismiss()
}
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
builder.setTitle("Sort by:")
.setSingleChoiceItems(
grpname,
checkedItem
) { _, pos -> compareInt = pos }
.setPositiveButton("Ascending", click)
.setNegativeButton("Descending", click)
.create().show()
}
return super.onOptionsItemSelected(item)
}
private fun sortDate(compareInt: Int, reversed: Boolean) {
val comparator: Comparator<MappedObject> = object : Comparator<MappedObject> {
override fun compare(o1: MappedObject, o2: MappedObject): Int {
when (compareInt) {
0 -> return o1.wholeDriverObject?.user_details?.profileName!!.compareTo(
o2.wholeDriverObject?.user_details?.profileName!!
)
1 -> {
var s1: String? = o1.wholeDriverObject?.driver_number
var s2: String? = o2.wholeDriverObject?.driver_number
if (o1.wholeDriverObject?.driver_number == null || (o1.wholeDriverObject!!
.driver_number == "0")
) {
s1 = ";"
}
if (o2.wholeDriverObject?.driver_number == null || (o2.wholeDriverObject!!
.driver_number == "0")
) {
s2 = ";"
}
return s1!!.compareTo((s2)!!)
}
else -> {
throw IOException("dfdfs")
}
// 2 -> return MainActivity.approvalsClass.getOverApprovalStatusCode(o1.wholeDriverObject) -
// MainActivity.approvalsClass.getOverApprovalStatusCode(o2.wholeDriverObject)
// else -> return MainActivity.approvalsClass.getOverApprovalStatusCode(
// o1.wholeDriverObject
// ) - MainActivity.approvalsClass.getOverApprovalStatusCode(o2.wholeDriverObject)
} }
} }
override fun onChildChanged(
type: ChangeEventType,
snapshot: DataSnapshot,
newIndex: Int,
oldIndex: Int
) {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
applyBinding { progressCircular.hide() }
}
override fun authorizationError() {
setNonView(NO_AUTHORIZATION)
}
override fun cannotRetrieve() {
setNonView(CANNOT_RETRIEVE)
}
override fun noConnection() {
setNonView(NO_CONNECTION)
}
override fun permissionsDenied() {
setNonView(NO_PERMISSION)
}
override fun emptyList() {
setNonView(EMPTY_RESULTS)
}
} }
sharedPreferences!!.edit().putInt(SORT, compareInt).apply()
sharedPreferences!!.edit().putBoolean(REVERSED, reversed).apply()
if (reversed) {
Collections.sort(mappedObjectList, comparator.reversed())
} else {
Collections.sort(mappedObjectList, comparator)
}
recyclerViewAdapter!!.notifyDataSetChanged()
} }
companion object { private fun showChangeNumberDialog(defaultNumber: String, uid: String) {
private val SORT: String = "SORT" val inputText = EditText(context).apply {
private val REVERSED: String = "REVERSED" setTag(R.string.driver_identifier, "DriverIdentifierInput")
setText(defaultNumber)
setSelectAllOnFocus(true)
doOnTextChanged { _, _, count, _ -> if (count > 6) showToast("Identifier cannot be larger than 6") }
}
val layout = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(28, 0, 56, 0)
addView(inputText)
}
AlertDialog.Builder(requireContext(), R.style.AppTheme_AppBarOverlay)
.setTitle("Change Driver Identifier")
.setView(layout)
.setPositiveButton("Submit") { _, _ ->
val input = inputText.text?.toString()
input?.let { viewModel.updateDriverNumber(uid, it) }
}.create().show()
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_calls_fragment, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
if (menuItem.itemId == R.id.archive) {
displaySortOptions()
}
return true
}
private fun displaySortOptions() {
val groupName: Array<String> = arrayOf("Driver Name", "Driver Number")
val defaultPosition = viewModel.getSelectionAsPosition()
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
builder.setTitle("Sort by:")
.setSingleChoiceItems(
groupName,
defaultPosition
) { _, pos ->
val option = SortOption.getSortOptionByLabel(groupName[pos])
viewModel.createFirebaseOptions(sort = option)
}
.create().show()
} }
} }

View File

@@ -1,102 +0,0 @@
package h_mal.appttude.com.driver.ui
import android.app.AlertDialog
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import h_mal.appttude.com.driver.admin.objects.UserObject
import h_mal.appttude.com.driver.admin.objects.wholeObject.MappedObject
import h_mal.appttude.com.driver.R
class RecyclerViewAdapter constructor(var context: Context?, var objects: List<MappedObject>?) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecyclerView.ViewHolder {
val viewCurrent: View =
LayoutInflater.from(context).inflate(R.layout.list_item_layout, viewGroup, false)
return ViewHolderMain(viewCurrent)
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
val viewHolderCurrent: ViewHolderMain = viewHolder as ViewHolderMain
val mappedObject: MappedObject = objects!!.get(i)
val `object`: UserObject? = mappedObject.wholeDriverObject?.user_details
if (`object`!!.profilePicString != null) {
Picasso.get()
.load(`object`.profilePicString)
.resize(128, 128)
.placeholder(R.drawable.choice_img_round)
.into(viewHolderCurrent.profilePicImage)
} else {
viewHolderCurrent.profilePicImage.setImageResource(R.drawable.choice_img_round)
}
viewHolderCurrent.userNameTextView.setText(`object`.profileName)
viewHolderCurrent.userEmailTextView.setText(`object`.profileEmail)
if (mappedObject.wholeDriverObject?.driver_number == null) {
viewHolderCurrent.driverNo.text = "0"
} else {
val s: String = mappedObject.wholeDriverObject?.driver_number.toString()
viewHolderCurrent.driverNo.text = s
}
viewHolderCurrent.driverNo.setOnClickListener {
val builder: AlertDialog.Builder = AlertDialog.Builder(
context
)
val input: EditText = EditText(context)
val layout: LinearLayout = LinearLayout(context)
layout.orientation = LinearLayout.VERTICAL
layout.setPadding(28, 0, 56, 0)
input.setText(viewHolderCurrent.driverNo.text.toString())
input.setSelectAllOnFocus(true)
layout.addView(input)
builder.setTitle("Change Driver Number")
.setView(layout)
.setPositiveButton(
"Submit"
) { dialog, which ->
}.create()
.show()
}
// viewHolderCurrent.profileApprovalImage.setImageResource(
// MainActivity.approvalsClass!!.getOverApprovalStatusCode(mappedObject.wholeDriverObject)
// )
viewHolderCurrent.itemView.setOnClickListener {
val bundle: Bundle = Bundle()
bundle.putParcelable("mapped", mappedObject)
// executeFragment(UserMainFragment(), bundle)
}
}
override fun getItemCount(): Int {
return objects!!.size
}
internal inner class ViewHolderMain constructor(listItemView: View) :
RecyclerView.ViewHolder(listItemView) {
var profilePicImage: ImageView
var userNameTextView: TextView
var userEmailTextView: TextView
// CardView statusCard;
var profileApprovalImage: ImageView
var driverNo: TextView
init {
profilePicImage = listItemView.findViewById(R.id.driverPic)
userNameTextView = listItemView.findViewById(R.id.username_text)
// statusCard = listItemView.findViewById(R.id.status_icon);
userEmailTextView = listItemView.findViewById(R.id.emailaddress_text)
profileApprovalImage = listItemView.findViewById(R.id.approval_iv)
driverNo = listItemView.findViewById(R.id.driver_no)
}
}
}

View File

@@ -1,30 +0,0 @@
package h_mal.appttude.com.driver.ui
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.GridView
import androidx.fragment.app.Fragment
import h_mal.appttude.com.driver.admin.objects.wholeObject.MappedObject
import h_mal.appttude.com.driver.R
class UserMainFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view: View = inflater.inflate(R.layout.fragment_user_main, container, false)
Log.i("UserMain", "onCreateView: height = " + view.height)
val mappedObject: MappedObject? = requireArguments().getParcelable<MappedObject>("mapped")
activity?.title = mappedObject?.wholeDriverObject?.user_details?.profileName
val listView: GridView = view.findViewById(R.id.approvals_list)
listView.adapter = ApprovalListAdapter(requireActivity(), arrayOf(mappedObject))
return view
}
}

View File

@@ -0,0 +1,26 @@
package h_mal.appttude.com.driver.ui.driverprofile
import h_mal.appttude.com.driver.base.DataViewerFragment
import h_mal.appttude.com.driver.databinding.FragmentDriverLicenseBinding
import h_mal.appttude.com.driver.model.DriversLicense
import h_mal.appttude.com.driver.utils.setGlideImage
import h_mal.appttude.com.driver.viewmodels.DriverLicenseViewModel
class DriverLicenseFragment :
DataViewerFragment<DriverLicenseViewModel, FragmentDriverLicenseBinding, DriversLicense>() {
override fun setupView(binding: FragmentDriverLicenseBinding) {
super.setupView(binding)
viewsToHide(binding.submit, binding.searchImage)
}
override fun setFields(data: DriversLicense) {
super.setFields(data)
applyBinding {
driversliImg.setGlideImage(data.licenseImageString)
licNo.setText(data.licenseNumber)
licExpiry.setText(data.licenseExpiry)
}
}
}

View File

@@ -0,0 +1,31 @@
package h_mal.appttude.com.driver.ui.driverprofile
import h_mal.appttude.com.driver.base.DataViewerFragment
import h_mal.appttude.com.driver.databinding.FragmentDriverProfileBinding
import h_mal.appttude.com.driver.model.DriverProfile
import h_mal.appttude.com.driver.utils.setGlideImage
import h_mal.appttude.com.driver.viewmodels.DriverProfileViewModel
class DriverProfileFragment :
DataViewerFragment<DriverProfileViewModel, FragmentDriverProfileBinding, DriverProfile>() {
override fun setupView(binding: FragmentDriverProfileBinding) {
super.setupView(binding)
viewsToHide(binding.submit, binding.addPhoto)
}
override fun setFields(data: DriverProfile) {
super.setFields(data)
applyBinding {
driverPic.setGlideImage(data.driverPic)
namesInput.setText(data.forenames)
addressInput.setText(data.address)
postcodeInput.setText(data.postcode)
dobInput.setText(data.dob)
niNumber.setText(data.ni)
dateFirst.setText(data.dateFirst)
}
}
}

View File

@@ -0,0 +1,26 @@
package h_mal.appttude.com.driver.ui.driverprofile
import h_mal.appttude.com.driver.base.DataViewerFragment
import h_mal.appttude.com.driver.databinding.FragmentPrivateHireLicenseBinding
import h_mal.appttude.com.driver.model.PrivateHireLicense
import h_mal.appttude.com.driver.utils.setGlideImage
import h_mal.appttude.com.driver.viewmodels.PrivateHireLicenseViewModel
class PrivateHireLicenseFragment : DataViewerFragment<PrivateHireLicenseViewModel, FragmentPrivateHireLicenseBinding, PrivateHireLicense>() {
override fun setupView(binding: FragmentPrivateHireLicenseBinding) {
super.setupView(binding)
viewsToHide(binding.submit, binding.uploadphlic)
}
override fun setFields(data: PrivateHireLicense) {
super.setFields(data)
applyBinding {
imageView2.setGlideImage(data.phImageString)
phNo.setText(data.phNumber)
phExpiry.setText(data.phExpiry)
}
}
}

View File

@@ -0,0 +1,45 @@
package h_mal.appttude.com.driver.ui.vehicleprofile
import android.net.Uri
import android.widget.ImageView
import h_mal.appttude.com.driver.base.DataViewerFragment
import h_mal.appttude.com.driver.databinding.FragmentInsuranceBinding
import h_mal.appttude.com.driver.model.Insurance
import h_mal.appttude.com.driver.utils.setGlideImage
import h_mal.appttude.com.driver.viewmodels.InsuranceViewModel
class InsuranceFragment :
DataViewerFragment<InsuranceViewModel, FragmentInsuranceBinding, Insurance>() {
override fun setupView(binding: FragmentInsuranceBinding) {
super.setupView(binding)
viewsToHide(binding.submit, binding.uploadInsurance)
}
private fun updateImageCarousal(list: List<Any?>) {
applyBinding {
carouselView.setImageClickListener(null)
carouselView.setImageListener { i: Int, imageView: ImageView ->
when (list[i]) {
is Uri -> {
imageView.setGlideImage(list[i] as Uri)
}
is String -> imageView.setGlideImage(list[i] as String)
}
}
carouselView.pageCount = list.size
}
}
override fun setFields(data: Insurance) {
super.setFields(data)
applyBinding {
insurer.setText(data.insurerName)
insuranceExp.setText(data.expiryDate)
data.photoStrings?.let { updateImageCarousal(it) }
}
}
}

View File

@@ -0,0 +1,26 @@
package h_mal.appttude.com.driver.ui.vehicleprofile
import h_mal.appttude.com.driver.base.DataViewerFragment
import h_mal.appttude.com.driver.databinding.FragmentLogbookBinding
import h_mal.appttude.com.driver.model.Logbook
import h_mal.appttude.com.driver.utils.setGlideImage
import h_mal.appttude.com.driver.viewmodels.LogbookViewModel
class LogbookFragment :
DataViewerFragment<LogbookViewModel, FragmentLogbookBinding, Logbook>() {
override fun setupView(binding: FragmentLogbookBinding) {
super.setupView(binding)
viewsToHide(binding.submit, binding.uploadLb)
}
override fun setFields(data: Logbook) {
super.setFields(data)
applyBinding {
logBookImg.setGlideImage(data.photoString)
v5cNo.setText(data.v5cnumber)
}
}
}

View File

@@ -0,0 +1,25 @@
package h_mal.appttude.com.driver.ui.vehicleprofile
import h_mal.appttude.com.driver.base.DataViewerFragment
import h_mal.appttude.com.driver.databinding.FragmentMotBinding
import h_mal.appttude.com.driver.model.Mot
import h_mal.appttude.com.driver.utils.setGlideImage
import h_mal.appttude.com.driver.viewmodels.MotViewModel
class MotFragment : DataViewerFragment<MotViewModel, FragmentMotBinding, Mot>() {
override fun setupView(binding: FragmentMotBinding) {
super.setupView(binding)
viewsToHide(binding.submit, binding.uploadmot)
}
override fun setFields(data: Mot) {
super.setFields(data)
applyBinding {
motImg.setGlideImage(data.motImageString)
motExpiry.setText(data.motExpiry)
}
}
}

View File

@@ -0,0 +1,28 @@
package h_mal.appttude.com.driver.ui.vehicleprofile
import h_mal.appttude.com.driver.base.DataViewerFragment
import h_mal.appttude.com.driver.databinding.FragmentPrivateHireLicenseBinding
import h_mal.appttude.com.driver.model.PrivateHireVehicle
import h_mal.appttude.com.driver.utils.setGlideImage
import h_mal.appttude.com.driver.viewmodels.PrivateHireVehicleViewModel
class PrivateHireVehicleFragment :
DataViewerFragment<PrivateHireVehicleViewModel, FragmentPrivateHireLicenseBinding, PrivateHireVehicle>() {
override fun setupView(binding: FragmentPrivateHireLicenseBinding) {
super.setupView(binding)
viewsToHide(binding.submit, binding.uploadphlic)
}
override fun setFields(data: PrivateHireVehicle) {
super.setFields(data)
applyBinding {
imageView2.setGlideImage(data.phCarImageString)
phNo.setText(data.phCarNumber)
phExpiry.setText(data.phCarExpiry)
}
}
}

View File

@@ -0,0 +1,32 @@
package h_mal.appttude.com.driver.ui.vehicleprofile
import h_mal.appttude.com.driver.base.DataViewerFragment
import h_mal.appttude.com.driver.databinding.FragmentVehicleSetupBinding
import h_mal.appttude.com.driver.model.VehicleProfile
import h_mal.appttude.com.driver.viewmodels.VehicleProfileViewModel
class VehicleProfileFragment :
DataViewerFragment<VehicleProfileViewModel, FragmentVehicleSetupBinding, VehicleProfile>() {
override fun setupView(binding: FragmentVehicleSetupBinding) {
super.setupView(binding)
viewsToHide(binding.submit)
binding.seizedCheckbox.isEnabled = false
}
override fun setFields(data: VehicleProfile) {
super.setFields(data)
applyBinding {
reg.setText(data.reg)
make.setText(data.make)
carModel.setText(data.model)
colour.setText(data.colour)
keeperName.setText(data.keeperName)
address.setText(data.keeperAddress)
postcode.setText(data.keeperPostCode)
startDate.setText(data.startDate)
seizedCheckbox.isChecked = data.isSeized
}
}
}

View File

@@ -0,0 +1,3 @@
package h_mal.appttude.com.driver.utils
const val FRAGMENT = "fragment"

View File

@@ -0,0 +1,104 @@
package h_mal.appttude.com.driver.viewmodels
import android.content.res.Resources
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.google.firebase.database.DatabaseReference
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.base.BaseViewModel
import h_mal.appttude.com.driver.data.FirebaseCompletion
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.USER_CONST
import h_mal.appttude.com.driver.model.ApprovalStatus
import h_mal.appttude.com.driver.objects.ApprovalsObject
import h_mal.appttude.com.driver.ui.driverprofile.DriverLicenseFragment
import h_mal.appttude.com.driver.ui.driverprofile.DriverProfileFragment
import h_mal.appttude.com.driver.ui.driverprofile.PrivateHireLicenseFragment
import h_mal.appttude.com.driver.ui.vehicleprofile.InsuranceFragment
import h_mal.appttude.com.driver.ui.vehicleprofile.LogbookFragment
import h_mal.appttude.com.driver.ui.vehicleprofile.MotFragment
import h_mal.appttude.com.driver.ui.vehicleprofile.PrivateHireVehicleFragment
import h_mal.appttude.com.driver.ui.vehicleprofile.VehicleProfileFragment
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.FRAGMENT
import h_mal.appttude.com.driver.utils.getDataFromDatabaseRef
class ApproverViewModel(
private val resources: Resources,
private val database: FirebaseDatabaseSource
) : BaseViewModel() {
private lateinit var name: String
private lateinit var docRef: DatabaseReference
private var score: ApprovalStatus? = null
fun init(args: Bundle) {
// Retried uid & fragment class name from args
val uid = args.getString(USER_CONST) ?: throw NullPointerException("No user Id was passed")
name = args.getString(FRAGMENT)
?: throw NullPointerException("No fragment name argument passed")
// Define a document name based on fragment class name
val documentName = when (name) {
resources.getString(R.string.driver_profile) -> ApprovalsObject::driver_details_approval.name
resources.getString(R.string.drivers_license) -> ApprovalsObject::driver_license_approval.name
resources.getString(R.string.private_hire_license) -> ApprovalsObject::private_hire_approval.name
resources.getString(R.string.vehicle_profile) -> ApprovalsObject::vehicle_details_approval.name
resources.getString(R.string.insurance) -> ApprovalsObject::insurance_details_approval.name
resources.getString(R.string.m_o_t) -> ApprovalsObject::mot_details_approval.name
resources.getString(R.string.log_book) -> ApprovalsObject::log_book_approval.name
resources.getString(R.string.private_hire_vehicle_license) -> ApprovalsObject::ph_car_approval.name
else -> {
throw StringIndexOutOfBoundsException("No resource for $name")
}
}
docRef = database.getDocumentApprovalRef(uid, documentName)
io {
doTryOperation("") {
val data = docRef.getDataFromDatabaseRef<Int>()
score = data?.let { ApprovalStatus.getByScore(it) } ?: ApprovalStatus.NOT_SUBMITTED
onSuccess(FirebaseCompletion.Default)
}
}
}
fun getFragmentClass(): Class<out Fragment> {
return when (name) {
resources.getString(R.string.driver_profile) -> DriverProfileFragment::class.java
resources.getString(R.string.drivers_license) -> DriverLicenseFragment::class.java
resources.getString(R.string.private_hire_license) -> PrivateHireLicenseFragment::class.java
resources.getString(R.string.vehicle_profile) -> VehicleProfileFragment::class.java
resources.getString(R.string.insurance) -> InsuranceFragment::class.java
resources.getString(R.string.m_o_t) -> MotFragment::class.java
resources.getString(R.string.log_book) -> LogbookFragment::class.java
resources.getString(R.string.private_hire_vehicle_license) -> PrivateHireVehicleFragment::class.java
else -> {
throw StringIndexOutOfBoundsException("No resource for $name")
}
}
}
fun approveDocument() {
updateDocument(ApprovalStatus.APPROVED)
}
fun declineDocument() {
updateDocument(ApprovalStatus.DENIED)
}
private fun updateDocument(approval: ApprovalStatus) {
if (approval == score) {
val result = if (approval == ApprovalStatus.APPROVED) "approved" else "declined"
onError("Document already $result")
return
}
io {
doTryOperation("Failed to decline document") {
database.postToDatabaseRed(docRef, approval.score)
onSuccess(approval)
}
}
}
}

View File

@@ -0,0 +1,12 @@
package h_mal.appttude.com.driver.viewmodels
import h_mal.appttude.com.driver.base.DataViewerBaseViewModel
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.model.DriversLicense
class DriverLicenseViewModel(
private val database: FirebaseDatabaseSource
) : DataViewerBaseViewModel<DriversLicense>() {
override fun getDatabaseRef(uid: String) = database.getDriverLicenseRef(uid)
}

View File

@@ -0,0 +1,61 @@
package h_mal.appttude.com.driver.viewmodels
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.model.ApprovalStatus
import h_mal.appttude.com.driver.objects.ApprovalsObject
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.getDataFromDatabaseRef
import kotlinx.coroutines.Job
import java.io.IOException
class DriverOverviewViewModel(
auth: FirebaseAuthentication,
private val database: FirebaseDatabaseSource
) : DataSubmissionBaseViewModel<ApprovalsObject>(auth, database, null) {
private var driverId: String? = null
override val databaseRef: DatabaseReference = database.getApprovalsRef(driverId ?: "")
override val storageRef: StorageReference? = null
override val objectName: String = "Approvals"
override fun getDataFromDatabase(): Job = retrieveDataFromDatabase<ApprovalsObject>()
fun loadDriverApprovals(driverId: String?) {
this.driverId = driverId
io {
doTryOperation("Failed to retrieve $objectName") {
val data = driverId?.let {
database.getApprovalsRef(it).getDataFromDatabaseRef()
} ?: ApprovalsObject()
val mappedData = mapApprovalsForView(data)
onSuccess(mappedData)
return@doTryOperation
}
}
}
private fun mapApprovalsForView(data: ApprovalsObject): List<Pair<String, ApprovalStatus>> {
val list = mutableListOf<Pair<String, ApprovalStatus>>()
return list.apply {
add(0, Pair("Driver Profile", getApprovalStatusByScore(data.driver_details_approval)))
add(1, Pair("Drivers License", getApprovalStatusByScore(data.driver_license_approval)))
add(2, Pair("Private Hire License", getApprovalStatusByScore(data.private_hire_approval)))
add(3, Pair("Vehicle Profile", getApprovalStatusByScore(data.vehicle_details_approval)))
add(4, Pair("Insurance", getApprovalStatusByScore(data.insurance_details_approval)))
add(5, Pair("M.O.T", getApprovalStatusByScore(data.mot_details_approval)))
add(6, Pair("Log book", getApprovalStatusByScore(data.log_book_approval)))
add(7, Pair("Private Hire Vehicle License", getApprovalStatusByScore(data.ph_car_approval)))
}
}
private fun getApprovalStatusByScore(score: Int): ApprovalStatus {
if (score == 0) return ApprovalStatus.NOT_SUBMITTED
return ApprovalStatus.getByScore(score) ?: throw IOException("No approval for score $score")
}
}

View File

@@ -0,0 +1,11 @@
package h_mal.appttude.com.driver.viewmodels
import h_mal.appttude.com.driver.base.DataViewerBaseViewModel
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.model.DriverProfile
class DriverProfileViewModel(
private val database: FirebaseDatabaseSource
) : DataViewerBaseViewModel<DriverProfile>() {
override fun getDatabaseRef(uid: String) = database.getDriverDetailsRef(uid)
}

View File

@@ -0,0 +1,12 @@
package h_mal.appttude.com.driver.viewmodels
import h_mal.appttude.com.driver.base.DataViewerBaseViewModel
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.model.Insurance
class InsuranceViewModel(
private val database: FirebaseDatabaseSource
) : DataViewerBaseViewModel<Insurance>() {
override fun getDatabaseRef(uid: String) = database.getInsuranceDetailsRef(uid)
}

View File

@@ -0,0 +1,12 @@
package h_mal.appttude.com.driver.viewmodels
import h_mal.appttude.com.driver.base.DataViewerBaseViewModel
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.model.Logbook
class LogbookViewModel(
private val database: FirebaseDatabaseSource
) : DataViewerBaseViewModel<Logbook>() {
override fun getDatabaseRef(uid: String) = database.getLogbookRef(uid)
}

View File

@@ -0,0 +1,12 @@
package h_mal.appttude.com.driver.viewmodels
import h_mal.appttude.com.driver.base.DataViewerBaseViewModel
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.model.Mot
class MotViewModel(
private val database: FirebaseDatabaseSource
) : DataViewerBaseViewModel<Mot>() {
override fun getDatabaseRef(uid: String) = database.getMotDetailsRef(uid)
}

View File

@@ -0,0 +1,12 @@
package h_mal.appttude.com.driver.viewmodels
import h_mal.appttude.com.driver.base.DataViewerBaseViewModel
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.model.PrivateHireLicense
class PrivateHireLicenseViewModel(
private val database: FirebaseDatabaseSource
) : DataViewerBaseViewModel<PrivateHireLicense>() {
override fun getDatabaseRef(uid: String) = database.getPrivateHireRef(uid)
}

View File

@@ -0,0 +1,12 @@
package h_mal.appttude.com.driver.viewmodels
import h_mal.appttude.com.driver.base.DataViewerBaseViewModel
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.model.PrivateHireVehicle
class PrivateHireVehicleViewModel(
private val database: FirebaseDatabaseSource
) : DataViewerBaseViewModel<PrivateHireVehicle>() {
override fun getDatabaseRef(uid: String) = database.getPrivateHireVehicleRef(uid)
}

View File

@@ -0,0 +1,65 @@
package h_mal.appttude.com.driver.viewmodels
import com.firebase.ui.database.FirebaseRecyclerOptions
import h_mal.appttude.com.driver.base.BaseViewModel
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.prefs.PreferenceProvider
import h_mal.appttude.com.driver.model.SortOption
import h_mal.appttude.com.driver.objects.WholeDriverObject
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.isNotNull
class SuperUserViewModel(
private val firebaseDatabaseSource: FirebaseDatabaseSource,
private val preferenceProvider: PreferenceProvider
) : BaseViewModel() {
fun retrieveDefaultFirebaseOptions() {
val optionLabel = preferenceProvider.getSortOption()
val option = SortOption.getSortOptionByLabel(optionLabel)
createFirebaseOptions(option)
}
fun createFirebaseOptions(sort: SortOption? = null) {
val ref = firebaseDatabaseSource.getUsersRef().orderByChild("role").startAt("driver").endAt("driver")
sort?.isNotNull { preferenceProvider.setSortOption(it.label) }
// val query = when(sort) {
// NAME, NUMBER -> ref.orderByChild(sort.key)
//// SortOption.APPROVAL -> TODO()
// else -> ref.orderByKey()
// }
val options = FirebaseRecyclerOptions.Builder<WholeDriverObject>()
.setQuery(ref, WholeDriverObject::class.java)
.build()
onSuccess(options)
}
fun updateDriverNumber(uid: String, input: String?) {
io {
doTryOperation("failed update driver identifier") {
// validate input
if (input.isNullOrBlank()) {
onError("No driver identifier provided")
return@doTryOperation
}
val text = if (input.length > 6) input.substring(0, 7) else input
firebaseDatabaseSource.run {
postToDatabaseRed(getDriverNumberRef(uid), text)
onSuccess(Unit)
}
}
}
}
fun getSelectionAsPosition(): Int {
val optionLabel = preferenceProvider.getSortOption()
return SortOption.getPositionByLabel(optionLabel) ?: -1
}
}

View File

@@ -0,0 +1,12 @@
package h_mal.appttude.com.driver.viewmodels
import h_mal.appttude.com.driver.base.DataViewerBaseViewModel
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.model.VehicleProfile
class VehicleProfileViewModel(
private val database: FirebaseDatabaseSource
) : DataViewerBaseViewModel<VehicleProfile>() {
override fun getDatabaseRef(uid: String) = database.getVehicleDetailsRef(uid)
}

View File

@@ -1,93 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_gravity="center"
app:cardCornerRadius="22dp"
app:cardBackgroundColor="@android:color/transparent"
app:cardElevation="0dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/cardviewoutline" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:id="@+id/approval_iv"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center"
tools:src="@drawable/pending" />
<View
android:id="@+id/view_1"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" />
<TextView
android:id="@+id/approval_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="17sp"
android:textStyle="bold"
tools:text="Private Hire License" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<FrameLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_margin="12dp">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<ImageView
android:id="@+id/archive_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_archive_black_24dp"
app:tint="@color/colour_three" />
</FrameLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="@+id/divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="@color/colour_ten"
android:paddingEnd="28dp"
app:layout_constraintLeft_toLeftOf="@id/approval_status"
app:layout_constraintRight_toRightOf="@id/approval_status"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlSymmetry" />
<ImageView
android:id="@+id/approval_iv"
android:layout_width="@dimen/status_size"
android:layout_height="@dimen/status_size"
android:layout_gravity="center"
android:layout_marginStart="28dp"
android:layout_marginTop="9dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/pending"
android:contentDescription="@string/approval_status" />
<TextView
android:id="@+id/approval_status"
style="@style/subheader"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="28dp"
android:layout_marginRight="28dp"
app:layout_constraintBottom_toBottomOf="@id/approval_iv"
app:layout_constraintLeft_toRightOf="@id/approval_iv"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/approval_iv"
tools:text="Pending Approval" />
<TextView
android:id="@+id/approval_text"
style="@style/headerStyle"
android:textSize="20sp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="0dp"
android:layout_marginEnd="16dp"
android:paddingBottom="9dp"
app:layout_constraintLeft_toLeftOf="@id/approval_status"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/approval_status"
tools:text="Private Hire License" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/empty_view_message"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,53 @@
<?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">
<ImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:src="@drawable/splash_screen"
android:scaleType="centerCrop"
android:paddingTop="@dimen/default_indicator_margin_horizontal"
android:layout_marginTop="@dimen/default_indicator_margin_horizontal"
app:layout_constraintHeight_percent="0.5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:importantForAccessibility="no" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/constraint_container">
<ImageView
android:id="@+id/icon"
android:layout_width="80dp"
android:layout_height="80dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/header"
android:src="@drawable/baseline_inbox_24"
android:contentDescription="@string/image_icon_for_feedback_view" />
<TextView
android:id="@+id/header"
style="@style/headerStyle"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:gravity="center"
android:text="@string/no_drivers_to_show"/>
<TextView
android:id="@+id/subtext"
style="@style/subheader"
app:layout_constraintTop_toBottomOf="@id/header"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:gravity="center"
android:text="@string/no_drivers_subtext"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.ApproverFragment">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/buttonContainer"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:layout="@layout/fragment_private_hire_license" />
<LinearLayout
android:id="@+id/buttonContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:gravity="center"
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
<com.google.android.material.button.MaterialButton
android:id="@+id/approve"
android:layout_marginLeft="12dp"
android:layout_marginRight="6dp"
android:layout_width="0dp"
android:layout_weight="1"
style="@style/TextButton.WithIcon"
app:icon="@drawable/baseline_check_24"
android:text="@string/approve" />
<com.google.android.material.button.MaterialButton
android:id="@+id/decline"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_marginLeft="6dp"
android:layout_marginRight="12dp"
style="@style/TextButton.WithIcon"
app:icon="@drawable/baseline_clear_24"
app:iconTint="@android:color/holo_red_light"
android:backgroundTint="@color/colour_ten"
android:text="@string/decline" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,16 +1,42 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/container"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.HomeSuperUserFragment"> tools:context=".ui.HomeSuperUserFragment">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_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"
android:orientation="vertical"
tools:listitem="@layout/list_item_layout"> tools:listitem="@layout/list_item_layout">
</androidx.recyclerview.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
</RelativeLayout> <ProgressBar
android:id="@+id/progress_circular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:visibility="gone"
tools:visibility="visible"/>
<include
android:id="@+id/empty_view"
layout="@layout/empty_users_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:visibility="gone"
tools:visibility="visible"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,17 +1,32 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
style="@style/parent_constraint_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.UserMainFragment"> tools:context=".ui.DriverOverviewFragment">
<GridView <androidx.cardview.widget.CardView
android:id="@+id/approvals_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:numColumns="2" android:layout_gravity="center"
android:rowCount="4" android:layout_marginBottom="12dp"
android:stretchMode="columnWidth" app:cardBackgroundColor="@color/colour_nine"
tools:listitem="@layout/approval_list_grid_item" /> app:cardCornerRadius="28dp"
app:cardElevation="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
</RelativeLayout> <ListView
android:id="@+id/approvals_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@color/colour_three"
tools:layout_height="658dp"
tools:listitem="@layout/approval_list_item" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -10,7 +10,7 @@
android:id="@+id/driverPic" android:id="@+id/driverPic"
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="50dp" android:layout_height="50dp"
android:layout_marginLeft="24dp" android:layout_marginStart="24dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
@@ -26,18 +26,19 @@
android:id="@+id/approval_iv" android:id="@+id/approval_iv"
android:layout_width="10dp" android:layout_width="10dp"
android:layout_height="10dp" android:layout_height="10dp"
android:layout_marginRight="3dp" android:layout_marginEnd="3dp"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
app:layout_constraintTop_toTopOf="@id/driverPic" app:layout_constraintTop_toTopOf="@id/driverPic"
app:layout_constraintRight_toRightOf="@id/driverPic" app:layout_constraintRight_toRightOf="@id/driverPic"
android:adjustViewBounds="true" android:adjustViewBounds="true"
tools:src="@android:drawable/presence_online" /> tools:src="@android:drawable/presence_online"
android:contentDescription="@string/user_status" />
<TextView <TextView
android:id="@+id/driver_no" android:id="@+id/driver_no"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginRight="24dp" android:layout_marginEnd="24dp"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textSize="12sp" android:textSize="12sp"
android:textStyle="bold" android:textStyle="bold"
@@ -48,7 +49,7 @@
<androidx.appcompat.widget.LinearLayoutCompat <androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginLeft="24dp" android:layout_marginStart="24dp"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@id/driverPic" app:layout_constraintBottom_toBottomOf="@id/driverPic"
app:layout_constraintLeft_toRightOf="@id/driverPic" app:layout_constraintLeft_toRightOf="@id/driverPic"

View File

@@ -5,11 +5,34 @@
android:id="@+id/main_navigation" android:id="@+id/main_navigation"
app:startDestination="@id/homeAdminFragment"> app:startDestination="@id/homeAdminFragment">
<activity
android:id="@+id/nav_user_settings"
android:name="h_mal.appttude.com.driver.ui.update.UpdateActivity"
android:label="fragment_profile"
tools:layout="@layout/update_activity" />
<fragment <fragment
android:id="@+id/homeAdminFragment" android:id="@+id/homeAdminFragment"
android:name="h_mal.appttude.com.driver.ui.HomeSuperUserFragment" android:name="h_mal.appttude.com.driver.ui.HomeSuperUserFragment"
android:label="fragment_home" android:label="fragment_home"
tools:layout="@layout/fragment_home_super_user"> tools:layout="@layout/fragment_home_super_user">
<action
android:id="@+id/action_homeAdminFragment_to_userMainFragment"
app:destination="@id/userMainFragment" />
</fragment> </fragment>
<fragment
android:id="@+id/userMainFragment"
android:name="h_mal.appttude.com.driver.ui.DriverOverviewFragment"
android:label="fragment_user_main"
tools:layout="@layout/fragment_user_main">
<action
android:id="@+id/to_approverFragment"
app:destination="@id/approverFragment" />
</fragment>
<fragment
android:id="@+id/approverFragment"
android:name="h_mal.appttude.com.driver.ui.ApproverFragment"
android:label="fragment_approver"
tools:layout="@layout/fragment_approver" />
</navigation> </navigation>

View File

@@ -0,0 +1,5 @@
<resources>
<string name="app_name">Driver Admin</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>

View File

@@ -1,17 +1,44 @@
package h_mal.appttude.com.driver package h_mal.appttude.com.driver
import android.app.Activity
import android.app.Instrumentation
import android.content.Intent
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri
import android.view.View
import android.widget.DatePicker
import android.widget.ListView
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.test.espresso.Espresso.onData import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewInteraction import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions 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.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.contrib.PickerActions
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import h_mal.appttude.com.driver.helpers.DataHelper
import h_mal.appttude.com.driver.helpers.EspressoHelper.waitForView
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.anything import org.hamcrest.CoreMatchers.anything
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import java.io.File
@SuppressWarnings("unused")
open class BaseTestRobot { open class BaseTestRobot {
fun fillEditText(resId: Int, text: String?): ViewInteraction = fun fillEditText(resId: Int, text: String?): ViewInteraction =
@@ -21,25 +48,133 @@ open class BaseTestRobot {
) )
fun clickButton(resId: Int): ViewInteraction = fun clickButton(resId: Int): ViewInteraction =
onView((withId(resId))).perform(ViewActions.click()) onView((withId(resId))).perform(click())
fun textView(resId: Int): ViewInteraction = onView(withId(resId)) fun matchView(resId: Int): ViewInteraction = onView(withId(resId))
fun matchViewWaitFor(resId: Int): ViewInteraction = waitForView(withId(resId))
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
.check(matches(ViewMatchers.withText(text))) .check(matches(withText(text)))
fun matchText(resId: Int, text: String): ViewInteraction = matchText(textView(resId), 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) { fun clickListItem(listRes: Int, position: Int) {
onData(anything()) onData(anything())
.inAdapterView(allOf(withId(listRes))) .inAdapterView(allOf(withId(listRes)))
.atPosition(position).perform(ViewActions.click()) .atPosition(position).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, text: String) {
matchView(recyclerId)
.perform(
// scrollTo will fail the test if no item matches.
RecyclerViewActions.actionOnItem<VH>(hasDescendant(withText(text)), click())
)
}
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> clickSubViewInRecycler(recyclerId: Int, text: String, subView: Int) {
scrollToRecyclerItem<VH>(recyclerId, text)
?.perform(
// scrollTo will fail the test if no item matches.
RecyclerViewActions.actionOnItem<VH>(
hasDescendant(withText(text)), object : ViewAction {
override fun getDescription(): String = "Matching recycler descendant"
override fun getConstraints(): Matcher<View>? = isRoot()
override fun perform(uiController: UiController?, view: View?) {
view?.findViewById<View>(subView)?.performClick()
}
}
)
)
} }
fun checkErrorOnTextEntry(resId: Int, errorMessage: String): ViewInteraction = fun checkErrorOnTextEntry(resId: Int, errorMessage: String): ViewInteraction =
onView(withId(resId)).check(matches(checkErrorMessage(errorMessage))) onView(withId(resId)).check(matches(checkErrorMessage(errorMessage)))
fun checkImageViewHasImage(resId: Int): ViewInteraction =
onView(withId(resId)).check(matches(checkImage()))
fun swipeDown(resId: Int): ViewInteraction =
onView(withId(resId)).perform(swipeDown())
fun getStringFromResource(@StringRes resId: Int): String = fun getStringFromResource(@StringRes resId: Int): String =
Resources.getSystem().getString(resId) Resources.getSystem().getString(resId)
fun selectDateInPicker(year: Int, month: Int, day: Int) {
onView(withClassName(Matchers.equalTo(DatePicker::class.java.name))).perform(
PickerActions.setDate(
year,
month,
day
)
)
}
fun selectSingleImageFromGallery(filePath: FormRobot.FilePath, openSelector: () -> Unit) {
Intents.init()
// Build the result to return when the activity is launched.
val resultData = Intent()
resultData.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
resultData.data = Uri.fromFile(File(FormRobot.FilePath.getFilePath(filePath)))
val result = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)
// Set up result stubbing when an intent sent to image picker is seen.
intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(result)
openSelector()
Intents.release()
}
fun selectMultipleImageFromGallery(filePaths: Array<String>, openSelector: () -> Unit) {
Intents.init()
// Build the result to return when the activity is launched.
val resultData = Intent()
val clipData = DataHelper.createClipData(filePaths)
resultData.clipData = clipData
val result = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)
// Set up result stubbing when an intent sent to "contacts" is seen.
intending(IntentMatchers.toPackage("android.intent.action.PICK")).respondWith(result)
openSelector()
Intents.release()
}
} }

View File

@@ -1,35 +1,65 @@
package h_mal.appttude.com.driver package h_mal.appttude.com.driver
import android.Manifest
import android.R
import android.app.Activity
import android.content.Context
import android.os.Build
import android.view.View import android.view.View
import android.view.WindowManager
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.*
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.IdlingResource import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.UiController import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.espresso.ViewAction import androidx.test.rule.GrantPermissionRule
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.platform.app.InstrumentationRegistry
import h_mal.appttude.com.driver.base.BaseActivity import h_mal.appttude.com.driver.base.BaseActivity
import h_mal.appttude.com.driver.helpers.BaseViewAction
import h_mal.appttude.com.driver.helpers.SnapshotRule
import org.hamcrest.CoreMatchers
import org.hamcrest.Description
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
import org.hamcrest.core.AllOf
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.ClassRule
import org.junit.Rule
import tools.fastlane.screengrab.Screengrab
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy
import tools.fastlane.screengrab.locale.LocaleTestRule
open class BaseUiTest<T : BaseActivity<*,*>>( open class BaseUiTest<T : BaseActivity<*, *>>(
private val activity: Class<T> private val activity: Class<T>
) { ) {
private lateinit var mActivityScenarioRule: ActivityScenario<T> private lateinit var mActivityScenarioRule: ActivityScenario<T>
private var mIdlingResource: IdlingResource? = null private var mIdlingResource: IdlingResource? = null
private lateinit var currentActivity: Activity
@get:Rule
var permissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
@get:Rule
var snapshotRule: SnapshotRule = SnapshotRule()
@Rule
@JvmField
var localeTestRule = LocaleTestRule()
@Before @Before
fun setup() { fun setup() {
Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy())
beforeLaunch() beforeLaunch()
mActivityScenarioRule = ActivityScenario.launch(activity) mActivityScenarioRule = ActivityScenario.launch(activity)
mActivityScenarioRule.onActivity { mActivityScenarioRule.onActivity {
mIdlingResource = it.getIdlingResource()!! mIdlingResource = it.getIdlingResource()!!
IdlingRegistry.getInstance().register(mIdlingResource) IdlingRegistry.getInstance().register(mIdlingResource)
afterLaunch(it)
} }
} }
@@ -41,7 +71,7 @@ open class BaseUiTest<T : BaseActivity<*,*>>(
} }
fun getResourceString(@StringRes stringRes: Int): String { fun getResourceString(@StringRes stringRes: Int): String {
return InstrumentationRegistry.getInstrumentation().targetContext.resources.getString( return getInstrumentation().targetContext.resources.getString(
stringRes stringRes
) )
} }
@@ -49,7 +79,7 @@ open class BaseUiTest<T : BaseActivity<*,*>>(
fun waitFor(delay: Long) { fun waitFor(delay: Long) {
onView(isRoot()).perform(object : ViewAction { onView(isRoot()).perform(object : ViewAction {
override fun getConstraints(): Matcher<View> = isRoot() override fun getConstraints(): Matcher<View> = isRoot()
override fun getDescription(): String? = "wait for $delay milliseconds" override fun getDescription(): String = "wait for $delay milliseconds"
override fun perform(uiController: UiController, v: View?) { override fun perform(uiController: UiController, v: View?) {
uiController.loopMainThreadForAtLeast(delay) uiController.loopMainThreadForAtLeast(delay)
} }
@@ -57,4 +87,55 @@ open class BaseUiTest<T : BaseActivity<*,*>>(
} }
open fun beforeLaunch() {} open fun beforeLaunch() {}
open fun afterLaunch(context: Context) {}
@Suppress("DEPRECATION")
fun checkToastMessage(message: String) {
onView(withText(message)).inRoot(object : TypeSafeMatcher<Root>() {
override fun describeTo(description: Description?) {
description?.appendText("is toast")
}
override fun matchesSafely(root: Root): Boolean {
root.run {
if (windowLayoutParams.get().type == WindowManager.LayoutParams.TYPE_TOAST) {
decorView.run {
if (windowToken === applicationWindowToken) {
// windowToken == appToken means this window isn't contained by any other windows.
// if it was a window for an activity, it would have TYPE_BASE_APPLICATION.
return true
}
}
}
}
return false
}
}
).check(matches(isDisplayed()))
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
waitFor(3500)
}
}
fun checkSnackBarDisplayedByMessage(message: String) {
onView(
CoreMatchers.allOf(
withId(com.google.android.material.R.id.snackbar_text),
withText(message)
)
).check(matches(isDisplayed()))
}
private fun getCurrentActivity(): Activity {
onView(AllOf.allOf(withId(R.id.content), isDisplayed()))
.perform(object : BaseViewAction() {
override fun setPerform(uiController: UiController?, view: View?) {
if (view?.context is Activity) {
currentActivity = view.context as Activity
}
}
})
return currentActivity
}
} }

View File

@@ -7,4 +7,8 @@ const val deleteAccountFirebase =
"http://10.0.2.2:9099/identitytoolkit.googleapis.com/v1/accounts:delete?key=$apiKey" "http://10.0.2.2:9099/identitytoolkit.googleapis.com/v1/accounts:delete?key=$apiKey"
const val USER_PASSWORD = "LetMeIn123!" const val USER_PASSWORD = "LetMeIn123!"
const val DRIVER_EMAIL = "existing-driver@driver.com"
const val ADMIN_EMAIL = "admin@driver.com"
const val PASSWORD = "test123456"

View File

@@ -1,7 +1,9 @@
package h_mal.appttude.com.driver package h_mal.appttude.com.driver
import android.graphics.drawable.BitmapDrawable
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import org.hamcrest.Description import org.hamcrest.Description
import org.hamcrest.Matcher import org.hamcrest.Matcher
@@ -11,7 +13,7 @@ import org.hamcrest.TypeSafeMatcher
/** /**
* Matcher for testing error of TextInputLayout * Matcher for testing error of TextInputLayout
*/ */
fun checkErrorMessage(expectedErrorText: String): Matcher<View?>? { fun checkErrorMessage(expectedErrorText: String): Matcher<View?> {
return object : TypeSafeMatcher<View?>() { return object : TypeSafeMatcher<View?>() {
override fun matchesSafely(view: View?): Boolean { override fun matchesSafely(view: View?): Boolean {
if (view is EditText) { if (view is EditText) {
@@ -28,3 +30,25 @@ fun checkErrorMessage(expectedErrorText: String): Matcher<View?>? {
} }
} }
fun checkImage(): Matcher<View?> {
return object : TypeSafeMatcher<View?>() {
override fun matchesSafely(view: View?): Boolean {
if (view is ImageView) {
return hasImage(view)
}
return false
}
override fun describeTo(d: Description?) {}
private fun hasImage(view: ImageView): Boolean {
val drawable = view.drawable
var hasImage = drawable != null
if (hasImage && drawable is BitmapDrawable) {
hasImage = drawable.bitmap != null
}
return hasImage
}
}
}

View File

@@ -5,18 +5,23 @@ import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.storage.FirebaseStorage import com.google.firebase.storage.FirebaseStorage
import h_mal.appttude.com.driver.base.BaseActivity import h_mal.appttude.com.driver.base.BaseActivity
import h_mal.appttude.com.driver.data.FirebaseAuthSource import h_mal.appttude.com.driver.data.FirebaseAuthSource
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.tasks.await import kotlinx.coroutines.tasks.await
import org.junit.After import org.junit.After
import org.junit.BeforeClass import org.junit.BeforeClass
open class FirebaseTest<T : BaseActivity<*,*>>( open class FirebaseTest<T : BaseActivity<*, *>>(
activity: Class<T>, activity: Class<T>,
private val registered: Boolean = false, private val registered: Boolean = false,
private val signedIn: Boolean = false private val signedIn: Boolean = false,
private val signOutAfterTest: Boolean = true
) : BaseUiTest<T>(activity) { ) : BaseUiTest<T>(activity) {
private val firebaseAuthSource by lazy { FirebaseAuthSource() } private val firebaseAuthSource by lazy { FirebaseAuthSource() }
private val firebaseDatabaseSource by lazy { FirebaseDatabaseSource() }
private val firebaseStorageSource by lazy { FirebaseStorageSource() }
private var email: String? = null private var email: String? = null
@@ -45,9 +50,10 @@ open class FirebaseTest<T : BaseActivity<*,*>>(
} }
@After @After
fun tearDownFirebase() = runBlocking { fun tearDownFirebase() {
removeUser() if (signOutAfterTest) {
firebaseAuthSource.logOut() firebaseAuthSource.logOut()
}
} }
suspend fun setupUser( suspend fun setupUser(
@@ -58,6 +64,14 @@ open class FirebaseTest<T : BaseActivity<*,*>>(
firebaseAuthSource.registerUser(signInEmail, password).await().user firebaseAuthSource.registerUser(signInEmail, password).await().user
} }
suspend fun login(
signInEmail: String,
password: String
) {
email = signInEmail
firebaseAuthSource.signIn(signInEmail, password).await()
}
// remove the user we created for testing // remove the user we created for testing
suspend fun removeUser() { suspend fun removeUser() {
try { try {
@@ -82,9 +96,6 @@ open class FirebaseTest<T : BaseActivity<*,*>>(
} }
fun getEmail(): String? { fun getEmail(): String? {
firebaseAuthSource.getUser()?.email?.let { return firebaseAuthSource.getUser()?.email ?: email
return it
}
return email
} }
} }

View File

@@ -0,0 +1,46 @@
package h_mal.appttude.com.driver
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.withId
import h_mal.appttude.com.driver.helpers.getImagePath
open class FormRobot : BaseTestRobot() {
fun submit() = clickButton(R.id.submit)
fun setDate(datePickerLaunchViewId: Int, year: Int, monthOfYear: Int, dayOfMonth: Int) {
onView(withId(datePickerLaunchViewId)).perform(click())
selectDateInPicker(year, monthOfYear, dayOfMonth)
// click ok in date picker
onView(withId(android.R.id.button1)).perform(click())
}
fun selectSingleImage(imagePickerLauncherViewId: Int, filePath: FilePath) {
selectSingleImageFromGallery(filePath) {
onView(withId(imagePickerLauncherViewId)).perform(click())
}
// click ok in date picker
}
fun selectMultipleImage(imagePickerLauncherViewId: Int, filePaths: Array<String>) {
selectMultipleImageFromGallery(filePaths) {
onView(withId(imagePickerLauncherViewId)).perform(click())
}
}
enum class FilePath(val path: String) {
PROFILE_PIC("driver_profile_pic.jpg"),
INSURANCE("driver_insurance.jpg"),
PRIVATE_HIRE("driver_license_private_hire.jpg"),
PRIVATE_HIRE_CAR("driver_license_private_hire_car.jpg"),
LOGBOOK("driver_logbook.jpg"),
MOT("driver_mot.jpg"),
LICENSE("driver_license_driver.jpg");
companion object {
fun getFilePath(filePath: FilePath): String {
return getImagePath(filePath.path)
}
}
}
}

View File

@@ -3,8 +3,12 @@ package h_mal.appttude.com.driver
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.* import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import java.io.IOException import java.io.IOException
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException

View File

@@ -0,0 +1,54 @@
package h_mal.appttude.com.driver.firebase.api
import h_mal.appttude.com.driver.firebase.model.*
import okhttp3.OkHttpClient
import okhttp3.Request
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.PUT
interface FirebaseApi {
@PUT("v1/accounts:signUp")
suspend fun signUp(@Body request: SignUpRequest): Response<SignUpResponse>
@PUT("v1/accounts:signInWithPassword")
suspend fun signInWithPassword(@Body request: SignUpRequest): Response<SignUpResponse>
@PUT("v1/accounts:sendOobCode")
suspend fun sendOobCode(@Body request: Map<String, String>): Response<OobCodeResponse>
@PUT("v1/accounts:resetPassword")
suspend fun resetPassword(@Body request: ResetPasswordRequest): Response<ResetPasswordResponse>
// invoke method creating an invocation of the api call
companion object {
operator fun invoke(): FirebaseApi {
val host = "10.0.2.2"
val baseUrl = "http://$host:9099/identitytoolkit.googleapis.com/"
val okkHttpclient = OkHttpClient.Builder()
.addInterceptor {
val original = it.request()
val url = original.url.newBuilder()
.addQueryParameter("key", "apikeydfasdfasdfasdf")
.build()
val requestBuilder = original.newBuilder().url(url)
val request: Request = requestBuilder.build()
it.proceed(request)
}
.build()
// creation of retrofit class
return Retrofit.Builder()
.client(okkHttpclient)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(FirebaseApi::class.java)
}
}
}

View File

@@ -0,0 +1,26 @@
package h_mal.appttude.com.driver.firebase.api
import h_mal.appttude.com.driver.firebase.model.SignUpRequest
import h_mal.appttude.com.driver.firebase.model.SignUpResponse
import kotlinx.coroutines.runBlocking
class FirebaseApiModule {
private val firebaseApi = FirebaseApi()
fun signUp(email: String, password: String): SignUpResponse? {
return runBlocking {
val req = SignUpRequest(email = email, password = password)
val response = firebaseApi.signUp(req)
response.body()
}
}
fun signIn(email: String, password: String): SignUpResponse? {
return runBlocking {
val req = SignUpRequest(email = email, password = password)
val response = firebaseApi.signInWithPassword(req)
response.body()
}
}
}

View File

@@ -0,0 +1,38 @@
package h_mal.appttude.com.driver.firebase.api
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import okhttp3.Interceptor
import java.io.IOException
class NetworkConnectionInterceptor(
context: Context
) : Interceptor {
private val applicationContext = context.applicationContext
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
if (!isInternetAvailable()) {
throw IOException("Make sure you have an active data connection")
}
return chain.proceed(chain.request())
}
private fun isInternetAvailable(): Boolean {
var result = false
val connectivityManager =
applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
connectivityManager?.let {
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply {
result = when {
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
else -> false
}
}
}
return result
}
}

View File

@@ -0,0 +1,9 @@
package h_mal.appttude.com.driver.firebase.model
data class OobCodeResponse(
val kind: String? = null,
val oobLink: String? = null,
val oobCode: String? = null,
val email: String? = null
)

View File

@@ -0,0 +1,10 @@
package h_mal.appttude.com.driver.firebase.model
data class ResetPasswordRequest(
val oldPassword: String? = null,
val tenantId: String? = null,
val newPassword: String? = null,
val oobCode: String? = null,
val email: String? = null
)

View File

@@ -0,0 +1,9 @@
package h_mal.appttude.com.driver.firebase.model
data class ResetPasswordResponse(
val requestType: String? = null,
val kind: String? = null,
val newEmail: String? = null,
val email: String? = null
)

View File

@@ -0,0 +1,7 @@
package h_mal.appttude.com.driver.firebase.model
data class SignUpRequest(
val password: String? = null,
val email: String? = null
)

View File

@@ -1,4 +1,4 @@
package h_mal.appttude.com.driver.firebase package h_mal.appttude.com.driver.firebase.model
data class SignUpResponse( data class SignUpResponse(
val expiresIn: String? = null, val expiresIn: String? = null,

View File

@@ -0,0 +1,17 @@
package h_mal.appttude.com.driver.helpers
import android.view.View
import org.hamcrest.BaseMatcher
import org.hamcrest.Description
class BaseMatcher: BaseMatcher<View>() {
override fun describeTo(description: Description?) {
TODO("Not yet implemented")
}
override fun matches(actual: Any?): Boolean {
TODO("Not yet implemented")
}
}

View File

@@ -0,0 +1,27 @@
package h_mal.appttude.com.driver.helpers
import android.view.View
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import org.hamcrest.Matcher
open class BaseViewAction: ViewAction {
override fun getDescription(): String? = setDescription()
override fun getConstraints(): Matcher<View> = setConstraints()
override fun perform(uiController: UiController?, view: View?) {
setPerform(uiController, view)
}
open fun setDescription(): String? {
return null
}
open fun setConstraints(): Matcher<View> {
return isAssignableFrom(View::class.java)
}
open fun setPerform(uiController: UiController?, view: View?) { }
}

View File

@@ -0,0 +1,14 @@
package h_mal.appttude.com.driver.helpers
import android.os.Environment
import java.io.File
/**
* File paths for images on device
*/
fun getImagePath(imageConst: String): String {
return File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
"/Camera/images/$imageConst"
).absolutePath
}

View File

@@ -0,0 +1,33 @@
package h_mal.appttude.com.driver.helpers
import android.content.ClipData
import android.content.ClipData.Item
import android.net.Uri
import java.io.File
object DataHelper {
fun createClipItem(filePath: String) = Item(
Uri.fromFile(
File(filePath)
)
)
fun createClipData(item: Item, mimeType: String = "text/uri-list") =
ClipData(null, arrayOf(mimeType), item)
fun createClipData(filePath: String) = createClipData(createClipItem(filePath))
fun createClipData(filePaths: Array<String>): ClipData {
val clipData = createClipData(filePaths[0])
val remainingFiles = filePaths.copyOfRange(1, filePaths.size - 1)
clipData.addFilePaths(remainingFiles)
return clipData
}
fun createClipData(uri: Uri) = createClipData(Item(uri))
fun ClipData.addFilePaths(filePaths: Array<String>) {
filePaths.forEach { addItem(createClipItem(it)) }
}
}

View File

@@ -0,0 +1,123 @@
package h_mal.appttude.com.driver.helpers
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 childviews
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")
}
}

View File

@@ -0,0 +1,20 @@
package h_mal.appttude.com.driver.helpers
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import tools.fastlane.screengrab.Screengrab
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy
/**
* Junit rule that takes a screenshot when a test fails.
*/
class SnapshotRule : TestWatcher() {
override fun failed(e: Throwable, description: Description) {
// Catch a screenshot on failure
Screengrab.screenshot("FAILURE-" + getScreenshotName(description))
}
fun getScreenshotName(description: Description): String {
return description.className.replace(".", "-") + "_" + description.methodName.replace(".", "-")
}
}

View File

@@ -1,6 +1,7 @@
package h_mal.appttude.com.driver.robots package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.BaseTestRobot import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.PASSWORD
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
@@ -21,4 +22,11 @@ class LoginRobot : BaseTestRobot() {
fun checkPasswordError(err: String) = checkErrorOnTextEntry(R.id.password, err) fun checkPasswordError(err: String) = checkErrorOnTextEntry(R.id.password, err)
fun attemptLogin(emailAddress: String, password: String = PASSWORD) {
matchViewWaitFor(R.id.email)
setEmail(emailAddress)
setPassword(password)
clickLogin()
}
} }

View File

@@ -8,17 +8,17 @@ class RegisterRobot : BaseTestRobot() {
fun setName(name: String) = fillEditText(R.id.name_register, name) fun setName(name: String) = fillEditText(R.id.name_register, name)
fun setEmail(email: String) = fillEditText(R.id.email_register, email) fun setEmail(email: String) = fillEditText(R.id.email, email)
fun setPassword(pass: String) = fillEditText(R.id.password_top, pass) fun setPassword(pass: String) = fillEditText(R.id.password_top, pass)
fun setPasswordConfirm(pass: String) = fillEditText(R.id.password_bottom, pass) fun setPasswordConfirm(pass: String) = fillEditText(R.id.password_bottom, pass)
fun clickLogin() = clickButton(R.id.email_sign_up) fun clickLogin() = clickButton(R.id.submit)
fun checkNameError(err: String) = checkErrorOnTextEntry(R.id.name_register, err) fun checkNameError(err: String) = checkErrorOnTextEntry(R.id.name_register, err)
fun checkEmailError(err: String) = checkErrorOnTextEntry(R.id.email_register, err) fun checkEmailError(err: String) = checkErrorOnTextEntry(R.id.email, err)
fun checkPasswordError(err: String) = checkErrorOnTextEntry(R.id.password_top, err) fun checkPasswordError(err: String) = checkErrorOnTextEntry(R.id.password_top, err)

View File

@@ -0,0 +1,13 @@
package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R
fun approver(func: ApproverRobot.() -> Unit) = ApproverRobot().apply { func() }
class ApproverRobot : BaseTestRobot() {
fun clickApprove() = clickButton(R.id.approve)
fun clickDecline() = clickButton(R.id.decline)
}

View File

@@ -0,0 +1,36 @@
package h_mal.appttude.com.driver.robots
import android.view.View
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.helpers.EspressoHelper.waitForView
import org.hamcrest.BaseMatcher
import org.hamcrest.CoreMatchers.anything
fun driverOverview(func: DriverOverviewRobot.() -> Unit) = DriverOverviewRobot().apply { func() }
class DriverOverviewRobot : BaseTestRobot() {
fun clickOnItemAtPosition(index: Int) =
onData(anything())
.inAdapterView(withId(R.id.approvals_list))
.atPosition(index)
.perform(click())
fun matchView(position: Int, status: String) =
onData(anything())
.inAdapterView(withId(R.id.approvals_list))
.atPosition(position)
.onChildView(withText(status))
.check(matches(isDisplayed()))
fun waitForListViewToDisplay() {
waitForView(withId(R.id.approval_status))
}
}

View File

@@ -0,0 +1,53 @@
package h_mal.appttude.com.driver.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withTagKey
import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.base.CustomViewHolder
import h_mal.appttude.com.driver.model.DatabaseStatus
fun homeAdmin(func: HomeAdminRobot.() -> Unit) = HomeAdminRobot().apply { func() }
class HomeAdminRobot : BaseTestRobot() {
fun waitUntilDisplayed() {
matchViewWaitFor(R.id.recycler_view)
}
fun openDrawer() {
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
}
fun closeDrawer() {
onView(withId(R.id.drawer_layout)).perform(DrawerActions.close())
}
fun updateProfile() {
openDrawer()
clickButton(R.id.nav_user_settings)
}
fun clickOnItem(anyText: String) =
clickViewInRecycler<CustomViewHolder<*>>(R.id.recycler_view, anyText)
fun clickOnDriverIdentifier(anyText: String) =
clickSubViewInRecycler<CustomViewHolder<*>>(R.id.recycler_view, anyText, R.id.driver_no)
fun submitDialog(text: String) {
onView(withTagKey(R.string.driver_identifier)).perform(
ViewActions.replaceText(text),
ViewActions.closeSoftKeyboard()
)
// Click OK
onView(withId(android.R.id.button1)).perform(ViewActions.click())
}
fun showNoPermissionsDisplay() {
matchViewWaitFor(R.id.header)
matchText(R.id.header, DatabaseStatus.NO_PERMISSION.header)
matchText(R.id.subtext, DatabaseStatus.NO_PERMISSION.subtext)
}
}

View File

@@ -0,0 +1,15 @@
package h_mal.appttude.com.driver.tests
import h_mal.appttude.com.driver.ADMIN_EMAIL
import h_mal.appttude.com.driver.FirebaseTest
import h_mal.appttude.com.driver.PASSWORD
import h_mal.appttude.com.driver.ui.MainActivity
import kotlinx.coroutines.runBlocking
open class AdminBaseTest: FirebaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() {
runBlocking {
login(ADMIN_EMAIL, PASSWORD)
}
}
}

View File

@@ -0,0 +1,89 @@
package h_mal.appttude.com.driver.tests
import androidx.test.espresso.Espresso
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.robots.approver
import h_mal.appttude.com.driver.robots.driverOverview
import h_mal.appttude.com.driver.robots.homeAdmin
import org.junit.Test
class DocumentApproverTest : AdminBaseTest() {
@Test
fun loginAsAdmin_approveDocumentForDriver_documentApproved() {
homeAdmin {
waitUntilDisplayed()
clickOnItem("kabirmhkhan@gmail.com")
}
// Approve check
driverOverview {
waitForListViewToDisplay()
clickOnItemAtPosition(0)
}
approver {
clickApprove()
checkToastMessage("Document already approved")
Espresso.pressBack()
}
driverOverview {
waitForListViewToDisplay()
clickOnItemAtPosition(2)
}
approver {
clickApprove()
Espresso.pressBack()
}
driverOverview {
waitForListViewToDisplay()
matchView(2, getResourceString(R.string.approved))
}
}
@Test
fun loginAsAdmin_declineDocumentForDriver_documentDeclined() {
homeAdmin {
waitUntilDisplayed()
// TODO: find a better way to waitw
waitFor(1200)
clickOnItem("kabirmhkhan@gmail.com")
}
// Decline check
driverOverview {
waitForListViewToDisplay()
clickOnItemAtPosition(3)
}
approver {
clickDecline()
checkToastMessage("Document already declined")
Espresso.pressBack()
}
driverOverview {
waitForListViewToDisplay()
clickOnItemAtPosition(1)
}
approver {
clickDecline()
Espresso.pressBack()
}
driverOverview {
waitForListViewToDisplay()
matchView(1, getResourceString(R.string.denied))
}
}
@Test
fun loginAsAdmin_verifyNoDocumentForNewDriver() {
homeAdmin {
waitUntilDisplayed()
clickOnItem("fanasid@gmail.com")
}
driverOverview {
waitForListViewToDisplay()
matchView(0, getResourceString(R.string.not_submitted))
clickOnItemAtPosition(0)
matchView(0, getResourceString(R.string.not_submitted))
}
}
}

View File

@@ -0,0 +1,36 @@
package h_mal.appttude.com.driver.tests
import h_mal.appttude.com.driver.ADMIN_EMAIL
import h_mal.appttude.com.driver.DRIVER_EMAIL
import h_mal.appttude.com.driver.FirebaseTest
import h_mal.appttude.com.driver.robots.homeAdmin
import h_mal.appttude.com.driver.robots.login
import h_mal.appttude.com.driver.ui.user.LoginActivity
import org.junit.Test
import java.io.IOException
class UserListTest : FirebaseTest<LoginActivity>(LoginActivity::class.java) {
@Test
fun loginAsAdmin_updateDriverIdentifier_loggedIn() {
login {
attemptLogin(ADMIN_EMAIL)
}
homeAdmin {
clickOnDriverIdentifier("rsaif660@gmail.com")
submitDialog("ID45")
}
}
@Test
fun loginAsUser_unableToSeeDrivers_loggedIn() {
// Test fails on CI
// login {
// attemptLogin(DRIVER_EMAIL)
// }
// homeAdmin {
// showNoPermissionsDisplay()
// }
}
}

View File

@@ -0,0 +1,17 @@
package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun delete(func: DeleteRobot.() -> Unit) = DeleteRobot().apply { func() }
class DeleteRobot : FormRobot() {
fun enterEmail(email: String) = fillEditText(R.id.email_update, email)
fun enterPassword(password: String) = fillEditText(R.id.password_top, password)
fun submitForm(email: String, password: String) {
enterEmail(email)
enterPassword(password)
submit()
}
}

View File

@@ -0,0 +1,13 @@
package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R
fun driverScreen(func: DriverScreenRobot.() -> Unit) = DriverScreenRobot().apply { func() }
class DriverScreenRobot : BaseTestRobot() {
fun driverProfile() = clickButton(R.id.driver_prof)
fun privateHireLicense() = clickButton(R.id.private_hire)
fun driverLicense() = clickButton(R.id.drivers_license)
}

View File

@@ -1,11 +1,31 @@
package h_mal.appttude.com.driver.robots package h_mal.appttude.com.driver.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.matcher.ViewMatchers.withId
import h_mal.appttude.com.driver.BaseTestRobot import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.R
fun home(func: HomeRobot.() -> Unit) = HomeRobot().apply { func() } fun home(func: HomeRobot.() -> Unit) = HomeRobot().apply { func() }
class HomeRobot : BaseTestRobot() { class HomeRobot : BaseTestRobot() {
fun checkTitleExists(title: String) = matchText(R.id.prova_title_tv, title) fun checkTitleExists(title: String) = matchText(matchViewWaitFor(R.id.prova_title_tv), title)
fun openDrawer() {
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
}
fun closeDrawer() {
onView(withId(R.id.drawer_layout)).perform(DrawerActions.close())
}
fun updateProfile() {
openDrawer()
clickButton(R.id.nav_user_settings)
}
fun openDriverProfile() = clickButton(R.id.driver)
fun openVehicleProfile() = clickButton(R.id.car)
fun requestProfile() = clickButton(R.id.request_driver_button)
} }

View File

@@ -0,0 +1,19 @@
package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun updateEmail(func: UpdateEmailRobot.() -> Unit) = UpdateEmailRobot().apply { func() }
class UpdateEmailRobot : FormRobot() {
fun enterEmail(email: String) = fillEditText(R.id.email_update, email)
fun enterPassword(password: String) = fillEditText(R.id.password_top, password)
fun enterNewEmail(email: String) = fillEditText(R.id.new_email, email)
fun submitForm(email: String, password: String, newEmail: String) {
enterEmail(email)
enterPassword(password)
enterNewEmail(newEmail)
submit()
}
}

View File

@@ -0,0 +1,20 @@
package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun updatePassword(func: UpdatePasswordRobot.() -> Unit) = UpdatePasswordRobot().apply { func() }
class UpdatePasswordRobot : FormRobot() {
fun enterEmail(email: String) = fillEditText(R.id.email_update, email)
fun enterPassword(password: String) = fillEditText(R.id.password_top, password)
fun enterNewPassword(email: String) = fillEditText(R.id.password_bottom, email)
fun submitDelete() = clickButton(R.id.submit)
fun submitForm(email: String, password: String, newPassword: String) {
enterEmail(email)
enterPassword(password)
enterNewPassword(newPassword)
submit()
}
}

View File

@@ -0,0 +1,17 @@
package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun updateProfile(func: UpdateProfileRobot.() -> Unit) = UpdateProfileRobot().apply { func() }
class UpdateProfileRobot : FormRobot() {
fun enterName(name: String) = fillEditText(R.id.update_name, name)
fun selectImage() = selectSingleImage(R.id.profile_img, FilePath.PROFILE_PIC)
fun submitForm(name: String) {
// selectImage()
enterName(name)
submit()
}
}

View File

@@ -0,0 +1,14 @@
package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R
fun update(func: UpdateRobot.() -> Unit) = UpdateRobot().apply { func() }
class UpdateRobot : BaseTestRobot() {
fun updateEmail() = clickButton(R.id.update_email_button)
fun updatePassword() = clickButton(R.id.update_password_button)
fun updateProfile() = clickButton(R.id.update_profile_button)
fun deleteProfile() = clickButton(R.id.delete_profile)
}

View File

@@ -0,0 +1,15 @@
package h_mal.appttude.com.driver.robots
import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.R
fun vehicleScreen(func: VehicleScreenRobot.() -> Unit) = VehicleScreenRobot().apply { func() }
class VehicleScreenRobot : BaseTestRobot() {
fun vehicleProfile() = clickButton(R.id.vehicle_prof)
fun insurance() = clickButton(R.id.insurance)
fun mot() = clickButton(R.id.mot)
fun logbook() = clickButton(R.id.logbook)
fun privateHireVehicleLicense() = clickButton(R.id.private_hire_vehicle_license)
}

View File

@@ -0,0 +1,27 @@
package h_mal.appttude.com.driver.robots.driver
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun driversLicense(func: DriversLicenseRobot.() -> Unit) = DriversLicenseRobot().apply { func() }
class DriversLicenseRobot : FormRobot() {
fun enterLicenseNumber(text: String) = fillEditText(R.id.lic_no, text)
fun enterLicenseExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.lic_expiry, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.search_image, FilePath.LICENSE)
fun submitForm(licenseNumber: String, year: Int, monthOfYear: Int, dayOfMonth: Int) {
selectImage()
enterLicenseNumber(licenseNumber)
enterLicenseExpiry(year, monthOfYear, dayOfMonth)
submit()
}
fun validate() {
checkImageViewHasImage(R.id.driversli_img)
}
}

Some files were not shown because too many files have changed in this diff Show More