Release 29072023

This commit is contained in:
2023-07-29 17:49:30 +01:00
committed by GitHub
parent 23f16626cd
commit dd19e663c7
225 changed files with 3836 additions and 2191 deletions

View File

@@ -7,8 +7,104 @@ 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.
# See: https://circleci.com/docs/2.0/orb-intro/
orbs:
android: circleci/android@1.0.3
android: circleci/android@2.3.0
commands:
setup_repo:
description: checkout repo and android dependencies
steps:
- checkout
- run:
name: Setup subtree for test data
command: |
git config --global user.email "$GIT_EMAIL"
git config --global user.name "$GIT_EMAIL"
git subtree add --prefix=driver_app_data https://github.com/hmalik144/driver_app_data main
- android/restore-gradle-cache
- run:
name: Download Dependencies
command: |
sudo chmod +x ./gradlew
./gradlew androidDependencies
# Setup files for build.
- run:
name: Setup variables for build
command: |
echo "$GOOGLE_SERVICES_KEY" > "app/google-services.json"
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
- 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
- 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
# Define a job to be invoked later in a workflow.
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
jobs:
@@ -23,76 +119,16 @@ jobs:
# See: https://circleci.com/docs/2.0/executor-types/
executor:
name: android/android-machine
tag: 2023.05.1
# Add steps to the job
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
steps:
# Checkout the code as the first step.
- checkout
# Setup files for build.
- run:
name: Setup variables for build
command: |
echo "$GOOGLE_SERVICES_KEY" > "app/google-services.json"
- 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:
flavour:
type: string
default: ""
executor:
name: android/android-machine
steps:
- run:
name: Setup variables for release
command: |
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
# Checkout the code and its submodule as the first step.
- setup_repo
- run_tests:
flavour: << parameters.flavour >>
- run_ui_tests:
flavour: << parameters.flavour >>
# Invoke jobs via workflows
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
workflows:
@@ -100,23 +136,30 @@ workflows:
build-release-driver:
jobs:
- build-and-test:
flavour: Driver
- assemble-and-release:
flavour: "Driver"
- android/deploy-to-play-store:
filters:
branches:
only:
- main_driver
requires:
- build-and-test
executor:
name: android/android-machine
tag: 2023.05.1
lane-name: deployDriver
build-release-admin:
jobs:
- build-and-test:
flavour: Admin
- assemble-and-release:
flavour: Admin
flavour: "Admin"
- android/deploy-to-play-store:
filters:
branches:
only: main_admin
only:
- main_driver
requires:
- build-and-test
executor:
name: android/android-machine
tag: 2023.05.1
lane-name: deployAdmin

12
.gitignore vendored
View File

@@ -9,9 +9,21 @@
/.idea/navEditor.xml
/.idea/misc.xml
/.idea/assetWizardSettings.xml
/.idea/shelf/
.DS_Store
/build
/captures
.externalNativeBuild
*.log
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,6 @@ plugins {
def relStorePassword = System.getenv("RELEASE_STORE_PASSWORD")
def relKeyPassword = System.getenv("RELEASE_KEY_PASSWORD")
def relKeyAlias = System.getenv("RELEASE_KEY_ALIAS")
def relStoreFile = System.getenv("RELEASE_KEYSTORE")
android {
compileSdkVersion 31
@@ -17,8 +16,8 @@ android {
applicationId "h_mal.appttude.com.driver"
minSdkVersion 24
targetSdkVersion 31
versionCode 6
versionName "1.6"
versionCode 7
versionName "2.0.0"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
boolean state = project.rootProject.file('local.properties').canRead()
@@ -41,7 +40,7 @@ android {
storePassword relStorePassword
keyPassword relKeyPassword
keyAlias relKeyAlias
// storeFile file(relStoreFile)
storeFile file('./keystore')
}
}
@@ -66,22 +65,25 @@ android {
flavorDimensions "Default"
productFlavors {
driver {
versionCode 6
versionName "1.0.5"
applicationId "h_mal.appttude.com.driver"
versionCode 7
versionName "2.0.0"
}
admin {
applicationIdSuffix ".admin"
applicationId "h_mal.appttude.com.driver.admin"
versionCode 4
versionName "0.0.5"
}
}
sourceSets {
driver {
java.srcDirs += 'src/driver/java'
manifest {
srcFile 'src/driver/AndroidManifest.xml'
}
}
admin {
java.srcDirs += 'src/admin/java'
manifest {
srcFile 'src/admin/AndroidManifest.xml'
}
@@ -108,6 +110,7 @@ dependencies {
implementation 'androidx.viewpager:viewpager:1.0.0'
implementation "androidx.legacy:legacy-support-v4:1.0.0"
testImplementation "junit:junit:4.13.2"
implementation "androidx.preference:preference-ktx:1.2.0"
/ * Android Espresso */
def testJunitVersion = "1.1.5"
def testRunnerVersion = "1.5.2"
@@ -117,6 +120,8 @@ dependencies {
androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espressoVersion"
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
androidTestImplementation "androidx.test:runner:$testRunnerVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
/ * Google play services */
implementation "com.google.android.gms:play-services-auth:20.4.1"
/ * Google firebase */
@@ -128,6 +133,7 @@ dependencies {
implementation "com.google.firebase:firebase-auth:$firebaseAuth"
implementation "com.google.firebase:firebase-storage:$firebaseStorage"
implementation "com.google.firebase:firebase-database:$firebaseDatabase"
implementation 'com.firebaseui:firebase-ui-database:8.0.2'
/ * Photoviewer */
implementation "com.github.chrisbanes:PhotoView:2.1.0"
/ * Picasso photo loader */
@@ -151,4 +157,10 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
/ * Kotlin Reflect */
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 {
var driver_details_approval: Int = 0
var driver_license_approval: Int = 0
var private_hire_approval: Int = 0
var vehicle_details_approval: Int = 0
var mot_details_approval: Int = 0
var insurance_details_approval: Int = 0
var log_book_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
}
}
data class ApprovalsObject (
var driver_details_approval: Int = 0,
var driver_license_approval: Int = 0,
var private_hire_approval: Int = 0,
var vehicle_details_approval: Int = 0,
var mot_details_approval: Int = 0,
var insurance_details_approval: Int = 0,
var log_book_approval: Int = 0,
var ph_car_approval: Int = 0,
)

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 java.util.*
import h_mal.appttude.com.driver.model.DriversLicense
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()
constructor(
driver_license: HashMap<String, DriversLicense>?,
private_hire: HashMap<String, PrivateHireLicense>?,
vehicle_details: HashMap<String, VehicleProfile>?,
insurance_details: HashMap<String, Insurance>?,
mot_details: HashMap<String, Mot>?,
log_book: HashMap<String, Logbook>?,
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
}
}
data class ArchiveObject(
var driver_license: HashMap<String, DriversLicense>? = HashMap(),
var private_hire: HashMap<String, PrivateHireLicense>? = HashMap(),
var vehicle_details: HashMap<String, VehicleProfile>? = HashMap(),
var insurance_details: HashMap<String, Insurance>? = HashMap(),
var mot_details: HashMap<String, Mot>? = HashMap(),
var log_book: HashMap<String, Logbook>? = HashMap(),
var ph_car: HashMap<String, PrivateHireVehicle>? = HashMap(),
)

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 {
var profileName: String? = null
var profileEmail: String? = null
var profilePicString: String? = null
constructor()
constructor(profileName: String?, profileEmail: String?, profilePicString: String?) {
this.profileName = profileName
this.profileEmail = profileEmail
this.profilePicString = profilePicString
}
}
@IgnoreExtraProperties
data class UserObject (
var profileName: String? = "",
var profileEmail: String? = "",
var profilePicString: String? = "",
)

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.admin.objects.wholeObject.VehicleProfile
import h_mal.appttude.com.driver.objects.wholeObject.DriverProfile
import h_mal.appttude.com.driver.objects.wholeObject.VehicleProfile
class WholeDriverObject {
var driver_profile: DriverProfile? = null
var role: String? = null
var archive: ArchiveObject? = null
var user_details: UserObject? = null
var vehicle_profile: VehicleProfile? = null
var approvalsObject: ApprovalsObject? = null
var driver_number: String? = null
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()
}
data class WholeDriverObject(
var driver_profile: DriverProfile? = DriverProfile(),
var role: String? = "",
var archive: ArchiveObject? = ArchiveObject(),
var user_details: UserObject? = UserObject(),
var vehicle_profile: VehicleProfile? = VehicleProfile(),
var approvalsObject: ApprovalsObject? = ApprovalsObject(),
var driver_number: String? = "",
)

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.DriversLicense
import h_mal.appttude.com.driver.model.PrivateHireLicense
class DriverProfile {
var driver_profile: DriverProfile? = null
var driver_license: DriversLicense? = null
var private_hire: PrivateHireLicense? = null
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()
}
data class DriverProfile(
var driver_profile: DriverProfile? = DriverProfile(),
var driver_license: DriversLicense? = DriversLicense(),
var private_hire: PrivateHireLicense? = PrivateHireLicense(),
)

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.Logbook
import h_mal.appttude.com.driver.model.PrivateHireVehicle
import h_mal.appttude.com.driver.model.Mot
import h_mal.appttude.com.driver.model.PrivateHireVehicle
import h_mal.appttude.com.driver.model.VehicleProfile
class VehicleProfile {
var insurance_details: Insurance? = null
var log_book: Logbook? = null
var mot_details: Mot? = null
var vehicle_details: VehicleProfile? = null
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
}
}
data class VehicleProfile (
var insurance_details: Insurance? = Insurance(),
var log_book: Logbook? = Logbook(),
var mot_details: Mot? = Mot(),
var vehicle_details: VehicleProfile? = VehicleProfile(),
var privateHireVehicle: PrivateHireVehicle? = PrivateHireVehicle()
)

View File

@@ -1,188 +1,57 @@
package h_mal.appttude.com.driver.ui
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import h_mal.appttude.com.driver.admin.objects.wholeObject.MappedObject
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.databinding.ApprovalListItemBinding
import h_mal.appttude.com.driver.model.ApprovalStatus
import h_mal.appttude.com.driver.utils.hide
import java.io.IOException
class ApprovalListAdapter(
private val activity: Activity,
objects: Array<MappedObject?>
) : ArrayAdapter<MappedObject?>(activity, 0, objects) {
private val context: Context,
private val data: List<Pair<String, ApprovalStatus>>,
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 {
var listItemView: View? = convertView
val binding: ApprovalListItemBinding
if (listItemView == null) {
listItemView = LayoutInflater.from(activity).inflate(
R.layout.approval_list_grid_item, parent, false
)
}
// approvalCode = getApprovalStatusCode(position)
// val textView: TextView = listItemView!!.findViewById(R.id.approval_text)
// textView.text = names.get(position)
// val imageView: ImageView = listItemView.findViewById(R.id.approval_iv)
// imageView.setImageResource(
// MainActivity.approvalsClass!!.setImageResource(
// approvalCode
// )
// )
// imageView.setOnClickListener {
// SetApprovalDialog(
// approvalCode,
// activity,
// mappedObject.userId,
// position,
// imageView
// )
// }
// 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)!!
// Inflate view binding into listview cell
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
}
// override fun getCount(): Int {
// return 8
// }
//
// 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)
// }
// }
val key: String = getItem(position)?.first ?: throw IOException("No document name provided")
val approvalStatus: ApprovalStatus? = getItem(position)?.second
binding.approvalText.text = key
approvalStatus?.let { item ->
item.score.takeIf { it != 0 }?.let {
binding.root.setOnClickListener { callback.invoke(key) }
}
binding.approvalIv.setImageResource(item.drawableId)
binding.approvalStatus.text = context.getString(item.stringId)
}
// hide divider for first cell
if (position == 0) binding.divider.hide()
return (listItemView)
}
fun updateAdapter(date: List<Pair<String, ApprovalStatus>>) {
clear()
addAll(date)
}
}

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
import android.app.AlertDialog
import android.content.DialogInterface
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.*
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.widget.EditText
import android.widget.LinearLayout
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.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 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.*
class HomeSuperUserFragment : Fragment() {
var users: DatabaseReference? = null
var mappedObjectList: MutableList<MappedObject>? = null
private var sharedPreferences: SharedPreferences? = null
private var sortOrder: Int = 0
private val sortDesc: Boolean = false
private var recyclerViewAdapter: RecyclerViewAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
mappedObjectList = ArrayList()
users!!.addValueEventListener(valueEventListener)
sharedPreferences = requireActivity().getSharedPreferences("PREFS", 0)
class HomeSuperUserFragment : BaseFragment<SuperUserViewModel, FragmentHomeSuperUserBinding>(),
MenuProvider {
private lateinit var adapter: FirebaseRecyclerAdapter<WholeDriverObject, CustomViewHolder<ListItemLayoutBinding>>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
viewModel.retrieveDefaultFirebaseOptions()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view: View = inflater.inflate(R.layout.fragment_home_super_user, container, false)
view.findViewById<RecyclerView>(R.id.recycler_view).apply {
layoutManager = LinearLayoutManager(context)
recyclerViewAdapter = RecyclerViewAdapter(context, mappedObjectList)
adapter = recyclerViewAdapter
override fun onSuccess(data: Any?) {
super.onSuccess(data)
when (data) {
is FirebaseRecyclerOptions<*> -> setAdapterToRecyclerView(data)
}
}
private fun setNonView(status: DatabaseStatus) {
applyBinding {
emptyView.run {
root.setOnClickListener(null)
root.visibility = View.VISIBLE
icon.setImageResource(status.drawable)
header.setText(status.header)
subtext.setText(status.subtext)
}
}
}
return view
@Suppress("UNCHECKED_CAST")
private fun setAdapterToRecyclerView(options: FirebaseRecyclerOptions<*>) {
applyBinding {
progressCircular.show()
if (recyclerView.adapter == null) {
// create an adapter for the first time
adapter = createAdapter(options = options as FirebaseRecyclerOptions<WholeDriverObject>)
recyclerView.adapter = adapter
recyclerView.setHasFixedSize(true)
adapter.startListening()
} else {
adapter.updateOptions(options as FirebaseRecyclerOptions<WholeDriverObject>)
}
}
}
var valueEventListener: ValueEventListener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
mappedObjectList!!.clear()
Log.i("Count ", "" + snapshot.childrenCount)
for (postSnapshot: DataSnapshot in snapshot.children) {
if ((postSnapshot.child("role").value.toString() == "driver")) {
mappedObjectList!!.add(
MappedObject(
postSnapshot.key, postSnapshot.getValue(
WholeDriverObject::class.java
)
)
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 onCancelled(databaseError: DatabaseError) {
override fun onDataChanged() {
super.onDataChanged()
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 onChildChanged(
type: ChangeEventType,
snapshot: DataSnapshot,
newIndex: Int,
oldIndex: Int
) {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
applyBinding { progressCircular.hide() }
}
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()
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)
}
}
}
private fun showChangeNumberDialog(defaultNumber: String, uid: String) {
val inputText = EditText(context).apply {
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(
grpname,
checkedItem
) { _, pos -> compareInt = pos }
.setPositiveButton("Ascending", click)
.setNegativeButton("Descending", click)
groupName,
defaultPosition
) { _, pos ->
val option = SortOption.getSortOptionByLabel(groupName[pos])
viewModel.createFirebaseOptions(sort = option)
}
.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)
}
}
}
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 val SORT: String = "SORT"
private val REVERSED: String = "REVERSED"
}
}

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"?>
<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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.HomeSuperUserFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="vertical"
tools:listitem="@layout/list_item_layout">
</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"?>
<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"
style="@style/parent_constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.UserMainFragment">
tools:context=".ui.DriverOverviewFragment">
<GridView
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="12dp"
app:cardBackgroundColor="@color/colour_nine"
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">
<ListView
android:id="@+id/approvals_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="2"
android:rowCount="4"
android:stretchMode="columnWidth"
tools:listitem="@layout/approval_list_grid_item" />
android:layout_height="wrap_content"
android:divider="@color/colour_three"
tools:layout_height="658dp"
tools:listitem="@layout/approval_list_item" />
</androidx.cardview.widget.CardView>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

@@ -5,11 +5,34 @@
android:id="@+id/main_navigation"
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
android:id="@+id/homeAdminFragment"
android:name="h_mal.appttude.com.driver.ui.HomeSuperUserFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home_super_user">
<action
android:id="@+id/action_homeAdminFragment_to_userMainFragment"
app:destination="@id/userMainFragment" />
</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>

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
import android.app.Activity
import android.app.Instrumentation
import android.content.Intent
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.recyclerview.widget.RecyclerView.ViewHolder
import androidx.test.espresso.Espresso.onData
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.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.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.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.anything
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import java.io.File
@SuppressWarnings("unused")
open class BaseTestRobot {
fun fillEditText(resId: Int, text: String?): ViewInteraction =
@@ -21,25 +48,133 @@ open class BaseTestRobot {
)
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
.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) {
onData(anything())
.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 =
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 =
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
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.WindowManager
import androidx.annotation.StringRes
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.*
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.rule.GrantPermissionRule
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.TypeSafeMatcher
import org.hamcrest.core.AllOf
import org.junit.After
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 lateinit var mActivityScenarioRule: ActivityScenario<T>
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
fun setup() {
Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy())
beforeLaunch()
mActivityScenarioRule = ActivityScenario.launch(activity)
mActivityScenarioRule.onActivity {
mIdlingResource = it.getIdlingResource()!!
IdlingRegistry.getInstance().register(mIdlingResource)
afterLaunch(it)
}
}
@@ -41,7 +71,7 @@ open class BaseUiTest<T : BaseActivity<*,*>>(
}
fun getResourceString(@StringRes stringRes: Int): String {
return InstrumentationRegistry.getInstrumentation().targetContext.resources.getString(
return getInstrumentation().targetContext.resources.getString(
stringRes
)
}
@@ -49,7 +79,7 @@ open class BaseUiTest<T : BaseActivity<*,*>>(
fun waitFor(delay: Long) {
onView(isRoot()).perform(object : ViewAction {
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?) {
uiController.loopMainThreadForAtLeast(delay)
}
@@ -57,4 +87,55 @@ open class BaseUiTest<T : BaseActivity<*,*>>(
}
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

@@ -8,3 +8,7 @@ const val deleteAccountFirebase =
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
import android.graphics.drawable.BitmapDrawable
import android.view.View
import android.widget.EditText
import android.widget.ImageView
import com.google.android.material.textfield.TextInputLayout
import org.hamcrest.Description
import org.hamcrest.Matcher
@@ -11,7 +13,7 @@ import org.hamcrest.TypeSafeMatcher
/**
* Matcher for testing error of TextInputLayout
*/
fun checkErrorMessage(expectedErrorText: String): Matcher<View?>? {
fun checkErrorMessage(expectedErrorText: String): Matcher<View?> {
return object : TypeSafeMatcher<View?>() {
override fun matchesSafely(view: View?): Boolean {
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 h_mal.appttude.com.driver.base.BaseActivity
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.tasks.await
import org.junit.After
import org.junit.BeforeClass
open class FirebaseTest<T : BaseActivity<*,*>>(
open class FirebaseTest<T : BaseActivity<*, *>>(
activity: Class<T>,
private val registered: Boolean = false,
private val signedIn: Boolean = false
private val signedIn: Boolean = false,
private val signOutAfterTest: Boolean = true
) : BaseUiTest<T>(activity) {
private val firebaseAuthSource by lazy { FirebaseAuthSource() }
private val firebaseDatabaseSource by lazy { FirebaseDatabaseSource() }
private val firebaseStorageSource by lazy { FirebaseStorageSource() }
private var email: String? = null
@@ -45,10 +50,11 @@ open class FirebaseTest<T : BaseActivity<*,*>>(
}
@After
fun tearDownFirebase() = runBlocking {
removeUser()
fun tearDownFirebase() {
if (signOutAfterTest) {
firebaseAuthSource.logOut()
}
}
suspend fun setupUser(
signInEmail: String = generateEmailAddress(),
@@ -58,6 +64,14 @@ open class FirebaseTest<T : BaseActivity<*,*>>(
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
suspend fun removeUser() {
try {
@@ -82,9 +96,6 @@ open class FirebaseTest<T : BaseActivity<*,*>>(
}
fun getEmail(): String? {
firebaseAuthSource.getUser()?.email?.let {
return it
}
return email
return firebaseAuthSource.getUser()?.email ?: 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.reflect.TypeToken
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.Response
import java.io.IOException
import kotlin.coroutines.resume
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(
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
import h_mal.appttude.com.driver.BaseTestRobot
import h_mal.appttude.com.driver.PASSWORD
import h_mal.appttude.com.driver.R
@@ -21,4 +22,11 @@ class LoginRobot : BaseTestRobot() {
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 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 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 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)

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
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.R
fun home(func: HomeRobot.() -> Unit) = HomeRobot().apply { func() }
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