Merge pull request #18 from hmalik144/firebase_rest_api

Firebase rest api & Test Suite
This commit is contained in:
2023-05-26 13:28:29 +01:00
committed by GitHub
149 changed files with 2664 additions and 1044 deletions

View File

@@ -79,7 +79,7 @@ jobs:
parameters:
flavour:
type: string
default: ""
default: "Driver"
executor:
name: android/android-machine
steps:
@@ -101,7 +101,7 @@ workflows:
build-release-driver:
jobs:
- build-and-test:
flavour: Driver
flavour: "Driver"
- assemble-and-release:
flavour: "Driver"
filters:
@@ -113,9 +113,9 @@ workflows:
build-release-admin:
jobs:
- build-and-test:
flavour: Admin
flavour: "Admin"
- assemble-and-release:
flavour: Admin
flavour: "Admin"
filters:
branches:
only: main_admin

View File

@@ -36,18 +36,18 @@ android {
}
}
signingConfigs {
release {
storePassword relStorePassword
keyPassword relKeyPassword
keyAlias relKeyAlias
storeFile file(relStoreFile)
}
}
// signingConfigs {
// release {
// storePassword relStorePassword
// keyPassword relKeyPassword
// keyAlias relKeyAlias
// storeFile file(relStoreFile)
// }
// }
buildTypes {
release {
signingConfig signingConfigs.release
// signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
@@ -66,22 +66,25 @@ android {
flavorDimensions "Default"
productFlavors {
driver {
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 +111,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 +121,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 +134,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 +158,8 @@ 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"
}

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,65 @@
package h_mal.appttude.com.driver.application
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
) : 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
)
else -> throw IllegalArgumentException("Unknown ViewModel class")
} as T
}
}
}

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,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,14 @@
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.*
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,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.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.*
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,77 @@
package h_mal.appttude.com.driver.ui
import android.app.Activity
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 android.widget.BaseAdapter
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.databinding.ApprovalListItemBinding
import h_mal.appttude.com.driver.utils.hide
class ApprovalListAdapter(
private val activity: Activity,
objects: Array<MappedObject?>
) : ArrayAdapter<MappedObject?>(activity, 0, objects) {
private val layoutInflater: LayoutInflater,
private var approvals: Map<String, Int?>,
private val callback: (String) -> Unit
) : BaseAdapter() {
override fun getCount(): Int = approvals.size
override fun getItem(position: Int): Map.Entry<String, Int?> = approvals.entries.elementAt(position)
override fun getItemId(position: Int): Long = position.toLong()
var mappedObject: MappedObject? = objects[0]
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)!!
binding = ApprovalListItemBinding.inflate(layoutInflater, parent, false)
listItemView = binding.root
listItemView.setTag(listItemView.id, binding)
} else {
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 = getItem(position).key
val itemValue = getItem(position).value
binding.approvalText.text = key
if (itemValue != 0) {
binding.root.setOnClickListener { callback.invoke(key) }
}
binding.approvalIv.setImageResource(getImageResourceBasedOnApproval(itemValue))
binding.approvalStatus.text = listItemView.context.getString(getStringResourceBasedOnApproval(itemValue))
if (position == 0) {
binding.divider.hide()
}
return (listItemView)
}
@DrawableRes
private fun getImageResourceBasedOnApproval(value: Int?): Int {
return when(value) {
0 -> R.drawable.denied
1 -> R.drawable.denied
2 -> R.drawable.pending
3 -> R.drawable.approved
else -> R.drawable.pending
}
}
@StringRes
private fun getStringResourceBasedOnApproval(value: Int?): Int {
return when(value) {
0 -> R.string.not_submitted
1 -> R.string.denied
2 -> R.string.pending
3 -> R.string.approved
else -> R.string.pending
}
}
fun updateAdapter(data: Map<String, Int?>) {
approvals = data
notifyDataSetChanged()
}
}

View File

@@ -0,0 +1,58 @@
package h_mal.appttude.com.driver.ui
import android.view.View
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.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
driverId = requireArguments().getString(USER_CONST) ?: throw IOException("No user ID has been passed")
viewModel.loadDriverApprovals(driverId)
}
override fun onSuccess(data: Any?) {
super.onSuccess(data)
@Suppress("UNCHECKED_CAST")
if (data is Map<*, *>) {
if (listView.adapter == null) {
listView.adapter = ApprovalListAdapter(layoutInflater, data as Map<String, Int?>) {
this.view?.applyNavigation(it)
}
listView.isScrollContainer = false
} else {
(listView.adapter as ApprovalListAdapter).updateAdapter(data as Map<String, Int?>)
}
}
}
private fun View.applyNavigation(key: String) {
val navId = when (key) {
context.getString(R.string.driver_profile) -> R.id.to_driverProfileFragment
context.getString(R.string.drivers_license) -> R.id.to_driverLicenseFragment
context.getString(R.string.private_hire_license) -> R.id.to_privateHireLicenseFragment
context.getString(R.string.vehicle_profile) -> R.id.to_vehicleProfileFragment
context.getString(R.string.insurance) -> R.id.to_insuranceFragment
context.getString(R.string.m_o_t) -> R.id.to_motFragment
context.getString(R.string.log_book) -> R.id.to_logbookFragment
context.getString(R.string.private_hire_vehicle_license) -> R.id.to_privateHireVehicleFragment
else -> {
throw StringIndexOutOfBoundsException("No resource for $key")
}
}
navigateTo(navId, driverId.toBundle(USER_CONST))
}
}

View File

@@ -1,153 +1,179 @@
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.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)
}
}
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 chat messages, show a view that invites the user to add a message.
if (itemCount == 0) {
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 connectionLost() {
requireContext().displayToast("No connection available")
}
}
}
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) context.displayToast("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,31 @@
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)
}
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,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,53 @@
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.objects.ApprovalsObject
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.getDataFromDatabaseRef
import kotlinx.coroutines.Job
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): Map<String, Int> {
return mutableMapOf<String, Int>().apply {
put("Driver Profile", data.driver_details_approval)
put("Drivers License", data.driver_license_approval)
put("Private Hire License", data.private_hire_approval)
put("Vehicle Profile", data.vehicle_details_approval)
put("Insurance", data.insurance_details_approval)
put("M.O.T", data.mot_details_approval)
put("Log book", data.log_book_approval)
put("Private Hire Vehicle License", data.ph_car_approval)
}.toMap()
}
}

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()
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.orderByKey(), 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

@@ -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

@@ -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"
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_layout"
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"/>
</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

@@ -5,11 +5,86 @@
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_driverLicenseFragment"
app:destination="@id/driverLicenseFragment" />
<action
android:id="@+id/to_driverProfileFragment"
app:destination="@id/driverProfileFragment" />
<action
android:id="@+id/to_privateHireLicenseFragment"
app:destination="@id/privateHireLicenseFragment" />
<action
android:id="@+id/to_insuranceFragment"
app:destination="@id/insuranceFragment" />
<action
android:id="@+id/to_logbookFragment"
app:destination="@id/logbookFragment" />
<action
android:id="@+id/to_motFragment"
app:destination="@id/motFragment" />
<action
android:id="@+id/to_privateHireVehicleFragment"
app:destination="@id/privateHireVehicleFragment" />
<action
android:id="@+id/to_vehicleProfileFragment"
app:destination="@id/vehicleProfileFragment" />
</fragment>
<fragment
android:id="@+id/driverLicenseFragment"
android:name="h_mal.appttude.com.driver.ui.driverprofile.DriverLicenseFragment"
android:label="DriverLicenseFragment" />
<fragment
android:id="@+id/driverProfileFragment"
android:name="h_mal.appttude.com.driver.ui.driverprofile.DriverProfileFragment"
android:label="fragment_driver_profile"
tools:layout="@layout/fragment_driver_profile" />
<fragment
android:id="@+id/privateHireLicenseFragment"
android:name="h_mal.appttude.com.driver.ui.driverprofile.PrivateHireLicenseFragment"
android:label="fragment_private_hire_license"
tools:layout="@layout/fragment_private_hire_license" />
<fragment
android:id="@+id/insuranceFragment"
android:name="h_mal.appttude.com.driver.ui.vehicleprofile.InsuranceFragment"
android:label="InsuranceFragment" />
<fragment
android:id="@+id/logbookFragment"
android:name="h_mal.appttude.com.driver.ui.vehicleprofile.LogbookFragment"
android:label="fragment_logbook"
tools:layout="@layout/fragment_logbook" />
<fragment
android:id="@+id/motFragment"
android:name="h_mal.appttude.com.driver.ui.vehicleprofile.MotFragment"
android:label="MotFragment" />
<fragment
android:id="@+id/privateHireVehicleFragment"
android:name="h_mal.appttude.com.driver.ui.vehicleprofile.PrivateHireVehicleFragment"
android:label="fragment_private_hire_vehicle"
tools:layout="@layout/fragment_private_hire_vehicle" />
<fragment
android:id="@+id/vehicleProfileFragment"
android:name="h_mal.appttude.com.driver.ui.vehicleprofile.VehicleProfileFragment"
android:label="fragment_vehicle_setup"
tools:layout="@layout/fragment_vehicle_setup" />
</navigation>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Driver Admin</string>
</resources>

View File

@@ -1,16 +1,37 @@
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 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.matcher.ViewMatchers.withId
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.*
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
open class BaseTestRobot {
@@ -21,25 +42,100 @@ 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(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 onView(withId(recyclerId))
.perform(
// scrollTo will fail the test if no item matches.
RecyclerViewActions.scrollTo<VH>(
hasDescendant(withText(text))
)
)
}
fun <VH : ViewHolder> clickViewInRecycler(recyclerId: Int, text: String) {
scrollToRecyclerItem<VH>(recyclerId, text)?.perform(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

@@ -16,7 +16,7 @@ import org.junit.After
import org.junit.Before
open class BaseUiTest<T : BaseActivity<*,*>>(
open class BaseUiTest<T : BaseActivity<*, *>>(
private val activity: Class<T>
) {
@@ -30,6 +30,7 @@ open class BaseUiTest<T : BaseActivity<*,*>>(
mActivityScenarioRule.onActivity {
mIdlingResource = it.getIdlingResource()!!
IdlingRegistry.getInstance().register(mIdlingResource)
afterLaunch()
}
}
@@ -57,4 +58,5 @@ open class BaseUiTest<T : BaseActivity<*,*>>(
}
open fun beforeLaunch() {}
open fun afterLaunch() {}
}

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

@@ -10,14 +10,13 @@ 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 var email: String? = null
companion object {
@@ -45,10 +44,11 @@ open class FirebaseTest<T : BaseActivity<*,*>>(
}
@After
fun tearDownFirebase() = runBlocking {
removeUser()
fun tearDownFirebase() {
if (signOutAfterTest) {
firebaseAuthSource.logOut()
}
}
suspend fun setupUser(
signInEmail: String = generateEmailAddress(),
@@ -82,9 +82,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,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

@@ -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,42 @@
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
fun homeAdmin(func: HomeAdminRobot.() -> Unit) = HomeAdminRobot().apply { func() }
class HomeAdminRobot : BaseTestRobot() {
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())
}
}

View File

@@ -0,0 +1,25 @@
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.robots.homeAdmin
import h_mal.appttude.com.driver.robots.login
import h_mal.appttude.com.driver.ui.user.LoginActivity
import org.junit.Test
class UserListTest : FirebaseTest<LoginActivity>(LoginActivity::class.java) {
@Test
fun loginAsAdmin_updateDriverIdentifier_loggedIn() {
login {
waitFor(1100)
attemptLogin(ADMIN_EMAIL)
}
homeAdmin {
clickOnDriverIdentifier("rsaif660@gmail.com")
submitDialog("ID45")
waitFor(5000)
}
}
}

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)
}
}

View File

@@ -0,0 +1,38 @@
package h_mal.appttude.com.driver.robots.driver
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun driversProfile(func: DriversProfileRobot.() -> Unit) = DriversProfileRobot().apply { func() }
class DriversProfileRobot : FormRobot() {
fun enterName(name: String) = fillEditText(R.id.names_input, name)
fun enterAddress(address: String) = fillEditText(R.id.address_input, address)
fun enterPostcode(postcode: String) = fillEditText(R.id.postcode_input, postcode)
fun enterDateOfBirth(dob: String) = fillEditText(R.id.dob_input, dob)
fun enterNINumber(niNumber: String) = fillEditText(R.id.ni_number, niNumber)
fun enterDateFirstAvailable(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.date_first, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.add_photo, FilePath.PROFILE_PIC)
fun submitForm(
name: String,
address: String,
postcode: String,
dob: String,
niNumber: String,
year: Int,
monthOfYear: Int,
dayOfMonth: Int
) {
selectImage()
enterName(name)
enterAddress(address)
enterPostcode(postcode)
enterDateOfBirth(dob)
enterNINumber(niNumber)
enterDateFirstAvailable(year, monthOfYear, dayOfMonth)
submit()
}
}

View File

@@ -0,0 +1,23 @@
package h_mal.appttude.com.driver.robots.driver
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun privateHireLicenseRobot(func: PrivateHireLicenseRobot.() -> Unit) =
PrivateHireLicenseRobot().apply { func() }
class PrivateHireLicenseRobot : FormRobot() {
fun enterLicenseNumber(text: String) = fillEditText(R.id.ph_no, text)
fun enterLicenseExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.ph_expiry, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.uploadphlic, FilePath.PRIVATE_HIRE)
fun submitForm(licenseNumber: String, year: Int, monthOfYear: Int, dayOfMonth: Int) {
selectImage()
enterLicenseNumber(licenseNumber)
enterLicenseExpiry(year, monthOfYear, dayOfMonth)
submit()
}
}

View File

@@ -0,0 +1,23 @@
package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.FormRobot.FilePath.Companion.getFilePath
import h_mal.appttude.com.driver.R
fun insurance(func: InsuranceRobot.() -> Unit) = InsuranceRobot().apply { func() }
class InsuranceRobot : FormRobot() {
fun enterInsurance(text: String) = fillEditText(R.id.insurer, text)
fun enterInsuranceExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.insurance_exp, year, monthOfYear, dayOfMonth)
fun selectImages() =
selectMultipleImage(R.id.uploadInsurance, arrayOf(getFilePath(FilePath.INSURANCE)))
fun submitForm(insurer: String, year: Int, monthOfYear: Int, dayOfMonth: Int) {
selectImages()
enterInsurance(insurer)
enterInsuranceExpiry(year, monthOfYear, dayOfMonth)
submit()
}
}

View File

@@ -0,0 +1,18 @@
package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun logbook(func: LogbookRobot.() -> Unit) = LogbookRobot().apply { func() }
class LogbookRobot : FormRobot() {
fun selectImages() = selectSingleImage(R.id.uploadmot, FilePath.MOT)
fun enterExpiryDate(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.mot_expiry, year, monthOfYear, dayOfMonth)
fun submitForm(year: Int, monthOfYear: Int, dayOfMonth: Int) {
selectImages()
enterExpiryDate(year, monthOfYear, dayOfMonth)
submit()
}
}

View File

@@ -0,0 +1,17 @@
package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun mot(func: MOTRobot.() -> Unit) = MOTRobot().apply { func() }
class MOTRobot : FormRobot() {
fun enterV5cNumber(v5c: String) = fillEditText(R.id.mot_expiry, v5c)
fun selectImages() = selectSingleImage(R.id.mot_expiry, FilePath.LOGBOOK)
fun submitForm(v5c: String) {
selectImages()
enterV5cNumber(v5c)
submit()
}
}

View File

@@ -0,0 +1,23 @@
package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
fun privateHireVehicleLicense(func: PrivateHireVehicleLicenseRobot.() -> Unit) =
PrivateHireVehicleLicenseRobot().apply { func() }
class PrivateHireVehicleLicenseRobot : FormRobot() {
fun enterLicenseNumber(text: String) = fillEditText(R.id.ph_no, text)
fun enterLicenseExpiry(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.ph_expiry, year, monthOfYear, dayOfMonth)
fun selectImage() = selectSingleImage(R.id.uploadphlic, FilePath.PRIVATE_HIRE)
fun submitForm(licenseNumber: String, year: Int, monthOfYear: Int, dayOfMonth: Int) {
selectImage()
enterLicenseNumber(licenseNumber)
enterLicenseExpiry(year, monthOfYear, dayOfMonth)
submit()
}
}

View File

@@ -0,0 +1,46 @@
package h_mal.appttude.com.driver.robots.vehicle
import h_mal.appttude.com.driver.FormRobot
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.helpers.EspressoHelper.setChecked
fun vehicleProfile(func: VehicleProfileRobot.() -> Unit) = VehicleProfileRobot().apply { func() }
class VehicleProfileRobot : FormRobot() {
fun enterRegistration(reg: String) = fillEditText(R.id.reg, reg)
fun enterMake(make: String) = fillEditText(R.id.make, make)
fun enterModel(model: String) = fillEditText(R.id.car_model, model)
fun enterColour(colour: String) = fillEditText(R.id.colour, colour)
fun enterAddress(address: String) = fillEditText(R.id.address, address)
fun enterPostcode(postCode: String) = fillEditText(R.id.postcode, postCode)
fun enterKeeperName(name: String) = fillEditText(R.id.keeper_name, name)
fun enterDateFirstAvailable(year: Int, monthOfYear: Int, dayOfMonth: Int) =
setDate(R.id.start_date, year, monthOfYear, dayOfMonth)
fun isSeized(seized: Boolean) = matchView(R.id.seized_checkbox).perform(setChecked(seized))
fun submitForm(
reg: String,
make: String,
model: String,
colour: String,
address: String,
postCode: String,
name: String,
year: Int,
monthOfYear: Int,
dayOfMonth: Int,
seized: Boolean = false
) {
enterRegistration(reg)
enterMake(make)
enterModel(model)
enterColour(colour)
enterAddress(address)
enterPostcode(postCode)
enterKeeperName(name)
enterDateFirstAvailable(year, monthOfYear, dayOfMonth)
isSeized(seized)
submit()
}
}

View File

@@ -33,7 +33,10 @@ class UserAuthenticationActivityTest : FirebaseTest<LoginActivity>(LoginActivity
}
home {
checkTitleExists(getResourceString(R.string.welcome_title))
updateProfile()
}
// TODO: update user details
}
}

View File

@@ -0,0 +1,43 @@
package h_mal.appttude.com.driver.tests.newUser
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.rule.GrantPermissionRule
import h_mal.appttude.com.driver.FirebaseTest
import h_mal.appttude.com.driver.R
import h_mal.appttude.com.driver.robots.*
import h_mal.appttude.com.driver.robots.driver.driversLicense
import h_mal.appttude.com.driver.ui.MainActivity
import org.junit.*
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class SubmitNewDataActivityTest :
FirebaseTest<MainActivity>(MainActivity::class.java, registered = true, signedIn = true) {
@get:Rule
var permissionRule =
GrantPermissionRule.grant(android.Manifest.permission.READ_EXTERNAL_STORAGE)
@Test
fun verifyUserRegistration_validUsernameAndPassword_loggedIn() {
home {
waitFor(2500)
checkTitleExists(getResourceString(R.string.welcome_title))
requestProfile()
openDriverProfile()
}
driverScreen {
driverLicense()
}
driversLicense {
submitForm("SAMPLE8456310LTU", 2022, 10, 2)
}
}
}

View File

@@ -1,5 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<debug-overrides>
<trust-anchors>
<certificates src="user" />
</trust-anchors>
</debug-overrides>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="h_mal.appttude.com.driver">
<application
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round">
</application>
</manifest>

View File

@@ -5,12 +5,14 @@ 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 storage: FirebaseStorageSource,
private val preferences: PreferenceProvider
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@@ -23,6 +25,11 @@ class ApplicationViewModelFactory(
auth,
storage
)
isAssignableFrom(RoleViewModel::class.java) -> RoleViewModel(
auth,
database,
storage
)
isAssignableFrom(DriverLicenseViewModel::class.java) -> DriverLicenseViewModel(
auth,
database,
@@ -59,11 +66,6 @@ class ApplicationViewModelFactory(
database,
storage
)
isAssignableFrom(RoleViewModel::class.java) -> RoleViewModel(
auth,
database,
storage
)
else -> throw IllegalArgumentException("Unknown ViewModel class")
} as T
}

View File

@@ -33,6 +33,7 @@ class MainActivity : DrawerActivity<MainViewModel, ActivityMainBinding>() {
setupDrawer(data)
}
}
}
private fun setupDrawer(user: FirebaseUser) {

View File

@@ -37,7 +37,7 @@ class DriverProfileFragment :
}
}
addPhoto.setOnClickListener { openGalleryWithPermissionRequest() }
submitDriver.setOnClickListener { submit() }
submit.setOnClickListener { submit() }
}
override fun submit() {

View File

@@ -34,7 +34,7 @@ class InsuranceFragment :
}
}
uploadInsurance.setOnClickListener { openGalleryWithPermissionRequest() }
submitIns.setOnClickListener { submit() }
submit.setOnClickListener { submit() }
}
}

View File

@@ -17,7 +17,7 @@ class LogbookFragment :
override fun setupView(binding: FragmentLogbookBinding) = binding.run {
v5cNo.setTextOnChange { model.v5cnumber = it }
uploadLb.setOnClickListener { openGalleryWithPermissionRequest() }
submitLb.setOnClickListener { submit() }
submit.setOnClickListener { submit() }
}
override fun submit() {

View File

@@ -24,7 +24,7 @@ class MotFragment : DataSubmissionBaseFragment<MotViewModel, FragmentMotBinding,
}
uploadmot.setOnClickListener { openGalleryWithPermissionRequest() }
submitMot.setOnClickListener {
submit.setOnClickListener {
validateEditTexts(motExpiry).isTrue {
viewModel.setDataInDatabase(model, picUri)
}

View File

@@ -30,7 +30,7 @@ class VehicleProfileFragment : DataSubmissionBaseFragment
}
seizedCheckbox.setOnCheckedChangeListener { _, res -> model.isSeized = res }
submitVehicle.setOnClickListener {
submit.setOnClickListener {
validateEditTexts(
reg,
make,

View File

@@ -20,7 +20,7 @@ class DriverLicenseViewModel(
override val storageRef: StorageReference = storage.driversLicenseStorageRef(uid)
override val objectName: String = "drivers license"
override fun getDataFromDatabase() = getDataClass<DriversLicense>()
override fun getDataFromDatabase() = retrieveDataFromDatabase<DriversLicense>()
override fun setDataInDatabase(data: DriversLicense, localImageUri: Uri?) = io {
doTryOperation("Failed to upload $objectName") {

View File

@@ -19,8 +19,7 @@ class DriverProfileViewModel(
override val databaseRef: DatabaseReference = database.getDriverDetailsRef(uid)
override val storageRef: StorageReference = storage.profileImageStorageRef(uid)
override val objectName: String = "drivers profile"
override fun getDataFromDatabase() = getDataClass<DriverProfile>()
override fun getDataFromDatabase() = retrieveDataFromDatabase<DriverProfile>()
override fun setDataInDatabase(data: DriverProfile, localImageUri: Uri?) = io {
doTryOperation("Failed to upload $objectName") {

View File

@@ -20,7 +20,7 @@ class InsuranceViewModel(
override val storageRef: StorageReference = storage.insuranceStorageRef(uid)
override val objectName: String = "insurance"
override fun getDataFromDatabase() = getDataClass<Insurance>()
override fun getDataFromDatabase() = retrieveDataFromDatabase<Insurance>()
override fun setDataInDatabase(data: Insurance, localImageUris: List<Uri?>?) = io {
doTryOperation("Failed to upload $objectName") {

View File

@@ -20,7 +20,7 @@ class LogbookViewModel(
override val storageRef: StorageReference = storage.logBookStorageRef(uid)
override val objectName: String = "Log book"
override fun getDataFromDatabase() = getDataClass<Logbook>()
override fun getDataFromDatabase() = retrieveDataFromDatabase<Logbook>()
override fun setDataInDatabase(data: Logbook, localImageUri: Uri?) = io {
doTryOperation("Failed to upload $objectName") {

View File

@@ -20,7 +20,7 @@ class MotViewModel(
override val storageRef: StorageReference? = storage.motStorageRef(uid)
override val objectName: String = "vehicle profile"
override fun getDataFromDatabase() = getDataClass<Mot>()
override fun getDataFromDatabase() = retrieveDataFromDatabase<Mot>()
override fun setDataInDatabase(data: Mot, localImageUri: Uri?) = io {
doTryOperation("Failed to upload $objectName") {

View File

@@ -20,7 +20,7 @@ class PrivateHireLicenseViewModel(
override val storageRef: StorageReference = storage.privateHireStorageRef(uid)
override val objectName: String = "private hire license"
override fun getDataFromDatabase() = getDataClass<PrivateHireLicense>()
override fun getDataFromDatabase() = retrieveDataFromDatabase<PrivateHireLicense>()
override fun setDataInDatabase(data: PrivateHireLicense, localImageUri: Uri?) = io {
doTryOperation("Failed to upload private hire license") {

View File

@@ -20,7 +20,7 @@ class PrivateHireVehicleViewModel(
override val storageRef: StorageReference = storage.privateHireVehicleStorageRef(uid)
override val objectName: String = "private hire vehicle license"
override fun getDataFromDatabase() = getDataClass<PrivateHireVehicle>()
override fun getDataFromDatabase() = retrieveDataFromDatabase<PrivateHireVehicle>()
override fun setDataInDatabase(data: PrivateHireVehicle, localImageUri: Uri?) = io {
doTryOperation("Failed to upload $objectName") {

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