diff --git a/.circleci/config.yml b/.circleci/config.yml index 9d82d27..d5a6ae2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,8 +7,104 @@ version: 2.1 # Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects. # See: https://circleci.com/docs/2.0/orb-intro/ orbs: - android: circleci/android@1.0.3 + android: circleci/android@2.3.0 +commands: + setup_repo: + description: checkout repo and android dependencies + steps: + - checkout + - run: + name: Setup subtree for test data + command: | + git config --global user.email "$GIT_EMAIL" + git config --global user.name "$GIT_EMAIL" + git subtree add --prefix=driver_app_data https://github.com/hmalik144/driver_app_data main + - android/restore-gradle-cache + - run: + name: Download Dependencies + command: | + sudo chmod +x ./gradlew + ./gradlew androidDependencies + # Setup files for build. + - run: + name: Setup variables for build + command: | + echo "$GOOGLE_SERVICES_KEY" > "app/google-services.json" + run_tests: + description: run non-instrumentation tests for flavour specified + parameters: + flavour: + type: string + default: "Driver" + steps: + # The next step will run the unit tests + - run: + name: Run non-instrumentation unit tests + command: | + ./gradlew test<< parameters.flavour >>DebugUnitTest --continue + - store_artifacts: + path: app/build/reports + destination: reports + - store_test_results: + path: app/build/test-results + run_ui_tests: + description: start firebase emulator and run ui tests for flavour specified + parameters: + flavour: + type: string + default: "AtlasWeather" + steps: + # Download and cache dependencies + - restore_cache: + keys: + - emulator-cache-v1- + # Install Firebase tools needed for firebase emulator + - run: + name: Install firebase tools + command: | + curl -sL firebase.tools | bash + # Then start the emulator and run the Instrumentation tests! + - android/start-emulator-and-run-tests: + post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest + test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest + system-image: system-images;android-25;google_apis;x86 + pull-data: true + pull-data-path: /storage/emulated/0/Android/data/ + pull-data-target: ~/app-data + pre-emulator-wait-steps: + # Start firebase emulator in the background while waiting to start testing + - run: + name: Start firebase emulator and while avd starts + command: | + firebase emulators:start --import=driver_app_data/export_directory + background: true + post-run-tests-steps: + # Save cache for firebase tools + - save_cache: + paths: + - ~/.cache/firebase/emulators/ + key: emulator-cache-v1-{{ epoch }} + # store test reports + - store_artifacts: + path: app/build/reports/androidTests/connected + destination: reports + # store screenshots for failed ui tests + - store_artifacts: + path: ~/app-data + destination: data + # Then publish the artifacts of the Firebase emulator logs! + - run: + name: save firebase emulator logs + command: | + mkdir -p tmp/firebase_logs + cp *.log tmp/firebase_logs + - store_artifacts: + path: tmp/firebase_logs + destination: logs + # Then publish the results of the Instrumentation tests! + - store_test_results: + path: app/build/outputs/androidTest-results/connected # Define a job to be invoked later in a workflow. # See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: @@ -23,76 +119,16 @@ jobs: # See: https://circleci.com/docs/2.0/executor-types/ executor: name: android/android-machine - + tag: 2023.05.1 # Add steps to the job # See: https://circleci.com/docs/2.0/configuration-reference/#steps steps: - # Checkout the code as the first step. - - checkout - # Setup files for build. - - run: - name: Setup variables for build - command: | - echo "$GOOGLE_SERVICES_KEY" > "app/google-services.json" - - run: - name: Grant execute permission for gradlew - command: | - chmod +x gradlew - # The next step will run the unit tests - - android/run-tests: - test-command: ./gradlew test<< parameters.flavour >>DebugUnitTest --continue - # Install Firebase tools needed for firebase emulator - - run: - name: Install firebase tools - command: | - curl -sL firebase.tools | bash - # Then start firebase emulator in the background - - run: - name: Start firebase emulator - command: | - firebase emulators:start - background: true - # Then start the emulator and run the Instrumentation tests! - - android/start-emulator-and-run-tests: - post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest - test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest - system-image: system-images;android-25;google_apis;x86 - # store test reports - - store_artifacts: - path: app/build/reports/androidTests/connected - destination: reports - # Then publish the artifacts of the Firebase emulator logs! - - run: - name: save firebase emulator logs - command: | - mkdir -p tmp/firebase_logs - cp *.log tmp/firebase_logs - - store_artifacts: - path: tmp/firebase_logs - destination: logs - # Then publish the results of the Instrumentation tests! - - store_test_results: - path: app/build/outputs/androidTest-results/connected - # Assemble - assemble-and-release: - # Parameters used for determining - parameters: - flavour: - type: string - default: "" - executor: - name: android/android-machine - steps: - - run: - name: Setup variables for release - command: | - echo "$RELEASE_KEYSTORE_BASE64" | base64 --decode > "android/app/release_keystore.jks" - echo "$GOOGLE_PLAY_KEY" > "android/playstore.json" - # And finally run the release build - - run: - name: Assemble release build - command: | - ./gradlew assembleDriverRelease + # Checkout the code and its submodule as the first step. + - setup_repo + - run_tests: + flavour: << parameters.flavour >> + - run_ui_tests: + flavour: << parameters.flavour >> # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: @@ -100,23 +136,30 @@ workflows: build-release-driver: jobs: - build-and-test: - flavour: Driver - - assemble-and-release: flavour: "Driver" + - android/deploy-to-play-store: filters: branches: only: - main_driver requires: - build-and-test + executor: + name: android/android-machine + tag: 2023.05.1 + lane-name: deployDriver build-release-admin: jobs: - build-and-test: - flavour: Admin - - assemble-and-release: - flavour: Admin + flavour: "Admin" + - android/deploy-to-play-store: filters: branches: - only: main_admin + only: + - main_driver requires: - - build-and-test \ No newline at end of file + - build-and-test + executor: + name: android/android-machine + tag: 2023.05.1 + lane-name: deployAdmin \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0fc0baf..ab59a65 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,21 @@ /.idea/navEditor.xml /.idea/misc.xml /.idea/assetWizardSettings.xml +/.idea/shelf/ .DS_Store /build /captures .externalNativeBuild *.log local +# Circleci +/.circleci/local_config.yml +/.circleci/run_local.bash +# Gem/fastlane +/Gemfile.lock +/google-play-key.json +/app/google-services.json +/fastlane/report.xml +# Firebase emulator +database-debug.log +firebase-debug.log diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index b589d56..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/dictionaries/h_mal.xml b/.idea/dictionaries/h_mal.xml deleted file mode 100644 index a2afb5c..0000000 --- a/.idea/dictionaries/h_mal.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - viewmodel - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 66ff961..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 6626cd0..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7a118b4 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/app/build.gradle b/app/build.gradle index db9d346..7a793a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,7 +9,6 @@ plugins { def relStorePassword = System.getenv("RELEASE_STORE_PASSWORD") def relKeyPassword = System.getenv("RELEASE_KEY_PASSWORD") def relKeyAlias = System.getenv("RELEASE_KEY_ALIAS") -def relStoreFile = System.getenv("RELEASE_KEYSTORE") android { compileSdkVersion 31 @@ -17,8 +16,8 @@ android { applicationId "h_mal.appttude.com.driver" minSdkVersion 24 targetSdkVersion 31 - versionCode 6 - versionName "1.6" + versionCode 7 + versionName "2.0.0" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' boolean state = project.rootProject.file('local.properties').canRead() @@ -41,7 +40,7 @@ android { storePassword relStorePassword keyPassword relKeyPassword keyAlias relKeyAlias -// storeFile file(relStoreFile) + storeFile file('./keystore') } } @@ -66,22 +65,25 @@ android { flavorDimensions "Default" productFlavors { driver { - versionCode 6 - versionName "1.0.5" + applicationId "h_mal.appttude.com.driver" + versionCode 7 + versionName "2.0.0" } admin { - applicationIdSuffix ".admin" + applicationId "h_mal.appttude.com.driver.admin" versionCode 4 versionName "0.0.5" } } sourceSets { driver { + java.srcDirs += 'src/driver/java' manifest { srcFile 'src/driver/AndroidManifest.xml' } } admin { + java.srcDirs += 'src/admin/java' manifest { srcFile 'src/admin/AndroidManifest.xml' } @@ -108,6 +110,7 @@ dependencies { implementation 'androidx.viewpager:viewpager:1.0.0' implementation "androidx.legacy:legacy-support-v4:1.0.0" testImplementation "junit:junit:4.13.2" + implementation "androidx.preference:preference-ktx:1.2.0" / * Android Espresso */ def testJunitVersion = "1.1.5" def testRunnerVersion = "1.5.2" @@ -117,6 +120,8 @@ dependencies { androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espressoVersion" implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion" androidTestImplementation "androidx.test:runner:$testRunnerVersion" + androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion" + androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion" / * Google play services */ implementation "com.google.android.gms:play-services-auth:20.4.1" / * Google firebase */ @@ -128,6 +133,7 @@ dependencies { implementation "com.google.firebase:firebase-auth:$firebaseAuth" implementation "com.google.firebase:firebase-storage:$firebaseStorage" implementation "com.google.firebase:firebase-database:$firebaseDatabase" + implementation 'com.firebaseui:firebase-ui-database:8.0.2' / * Photoviewer */ implementation "com.github.chrisbanes:PhotoView:2.1.0" / * Picasso photo loader */ @@ -151,4 +157,10 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:4.10.0' / * Kotlin Reflect */ implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.10" + / * Retrofit2 */ + def retrofit_version = "2.9.0" + androidTestImplementation "com.squareup.retrofit2:retrofit:$retrofit_version" + androidTestImplementation "com.squareup.retrofit2:converter-gson:$retrofit_version" + / * screenshot library */ + androidTestImplementation 'tools.fastlane:screengrab:2.1.1' } diff --git a/app/src/admin/AndroidManifest.xml b/app/src/admin/AndroidManifest.xml deleted file mode 100644 index 0dd5de2..0000000 --- a/app/src/admin/AndroidManifest.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt b/app/src/admin/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt new file mode 100644 index 0000000..6bb156a --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt @@ -0,0 +1,68 @@ +package h_mal.appttude.com.driver.application + +import android.content.res.Resources +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import h_mal.appttude.com.driver.data.FirebaseAuthSource +import h_mal.appttude.com.driver.data.FirebaseDatabaseSource +import h_mal.appttude.com.driver.data.FirebaseStorageSource +import h_mal.appttude.com.driver.data.prefs.PreferenceProvider +import h_mal.appttude.com.driver.viewmodels.* + +class ApplicationViewModelFactory( + private val auth: FirebaseAuthSource, + private val database: FirebaseDatabaseSource, + private val storage: FirebaseStorageSource, + private val preferences: PreferenceProvider, + private val resources: Resources +) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + with(modelClass) { + return when { + isAssignableFrom(UserViewModel::class.java) -> UserViewModel(auth) + isAssignableFrom(MainViewModel::class.java) -> MainViewModel(auth, database) + isAssignableFrom(UpdateUserViewModel::class.java) -> UpdateUserViewModel( + auth, + storage + ) + isAssignableFrom(RoleViewModel::class.java) -> RoleViewModel( + auth, + database, + storage + ) + + isAssignableFrom(DriverLicenseViewModel::class.java) -> DriverLicenseViewModel( + database + ) + isAssignableFrom(DriverProfileViewModel::class.java) -> DriverProfileViewModel( + database + ) + isAssignableFrom(PrivateHireLicenseViewModel::class.java) -> PrivateHireLicenseViewModel( + database + ) + isAssignableFrom(VehicleProfileViewModel::class.java) -> VehicleProfileViewModel( + database + ) + isAssignableFrom(InsuranceViewModel::class.java) -> InsuranceViewModel(database) + isAssignableFrom(MotViewModel::class.java) -> MotViewModel(database) + isAssignableFrom(LogbookViewModel::class.java) -> LogbookViewModel(database) + isAssignableFrom(PrivateHireVehicleViewModel::class.java) -> PrivateHireVehicleViewModel( + database + ) + isAssignableFrom(DriverOverviewViewModel::class.java) -> DriverOverviewViewModel( + auth, + database + ) + isAssignableFrom(SuperUserViewModel::class.java) -> SuperUserViewModel( + database, + preferences + ) + isAssignableFrom(ApproverViewModel::class.java) -> ApproverViewModel(resources , database) + else -> throw IllegalArgumentException("Unknown ViewModel class") + } as T + } + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/application/DriverApplication.kt b/app/src/admin/java/h_mal/appttude/com/driver/application/DriverApplication.kt new file mode 100644 index 0000000..6f4a8b6 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/application/DriverApplication.kt @@ -0,0 +1,23 @@ +package h_mal.appttude.com.driver.application + +import h_mal.appttude.com.driver.data.prefs.PreferenceProvider +import org.kodein.di.generic.bind +import org.kodein.di.generic.instance +import org.kodein.di.generic.provider +import org.kodein.di.generic.singleton + +class DriverApplication : BaseApplication() { + + override val flavourModule = super.flavourModule.copy { + bind() from singleton { PreferenceProvider(this@DriverApplication) } + bind() from provider { + ApplicationViewModelFactory( + instance(), + instance(), + instance(), + instance(), + instance() + ) + } + } +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/base/DataViewerBaseViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/base/DataViewerBaseViewModel.kt new file mode 100644 index 0000000..f0dcb82 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/base/DataViewerBaseViewModel.kt @@ -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 : 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(0) + io { + doTryOperation("Failed to retrieve ${clazz.simpleName}") { + val data = getDatabaseRef(uid).getDataFromDatabaseRef(clazz.java) + onSuccess(data ?: FirebaseCompletion.Default) + } + } + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/base/DataViewerFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/base/DataViewerFragment.kt new file mode 100644 index 0000000..3d991aa --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/base/DataViewerFragment.kt @@ -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, VB : ViewBinding, T : Any> : + BaseFragment() { + + 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(2)) }?.isTrue { + setFields(data as T) + } + } + + open fun setFields(data: T) {} + + fun viewsToHide(vararg view: View) { + view.forEach { it.hide() } + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/model/ApprovalStatus.kt b/app/src/admin/java/h_mal/appttude/com/driver/model/ApprovalStatus.kt new file mode 100644 index 0000000..75fc57b --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/model/ApprovalStatus.kt @@ -0,0 +1,19 @@ +package h_mal.appttude.com.driver.model + +import h_mal.appttude.com.driver.R + +enum class ApprovalStatus(val stringId: Int, val drawableId: Int, val score: Int) { + NOT_SUBMITTED(R.string.not_submitted, R.drawable.denied, 0), + DENIED(R.string.denied, R.drawable.denied, 1), + PENDING_APPROVAL(R.string.pending, R.drawable.pending, 2), + APPROVED(R.string.approved, R.drawable.approved, 3); + + companion object { + infix fun getByScore(value: Int): ApprovalStatus? = + ApprovalStatus.values().firstOrNull { it.score == value } + infix fun getByStringId(value: Int): ApprovalStatus? = + ApprovalStatus.values().firstOrNull { it.stringId == value } + infix fun getByDrawableId(value: Int): ApprovalStatus? = + ApprovalStatus.values().firstOrNull { it.drawableId == value } + } +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/model/DatabaseStatus.kt b/app/src/admin/java/h_mal/appttude/com/driver/model/DatabaseStatus.kt new file mode 100644 index 0000000..3fba046 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/model/DatabaseStatus.kt @@ -0,0 +1,27 @@ +package h_mal.appttude.com.driver.model + +import h_mal.appttude.com.driver.R + +enum class DatabaseStatus(val drawable: Int, val header: Int, val subtext: Int) { + NO_CONNECTION(R.drawable.baseline_inbox_24, R.string.no_connection, R.string.no_connection_subtext), + NO_PERMISSION( + R.drawable.baseline_inbox_24, + R.string.no_permission, + R.string.no_permission_subtext + ), + CANNOT_RETRIEVE( + R.drawable.baseline_inbox_24, + R.string.cannot_retrieve, + R.string.cannot_retrieve_subtext + ), + NO_AUTHORIZATION( + R.drawable.baseline_inbox_24, + R.string.no_authorization, + R.string.no_authorization_subtext + ), + EMPTY_RESULTS( + R.drawable.baseline_inbox_24, + R.string.no_drivers_to_show, + R.string.no_drivers_subtext + ) +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/model/SortOption.kt b/app/src/admin/java/h_mal/appttude/com/driver/model/SortOption.kt new file mode 100644 index 0000000..d51c199 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/model/SortOption.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/objects/ApprovalsObject.kt b/app/src/admin/java/h_mal/appttude/com/driver/objects/ApprovalsObject.kt index 515307e..47a4493 100644 --- a/app/src/admin/java/h_mal/appttude/com/driver/objects/ApprovalsObject.kt +++ b/app/src/admin/java/h_mal/appttude/com/driver/objects/ApprovalsObject.kt @@ -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 - } -} \ No newline at end of file +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, +) \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/objects/ArchiveObject.kt b/app/src/admin/java/h_mal/appttude/com/driver/objects/ArchiveObject.kt index 6595886..2700e93 100644 --- a/app/src/admin/java/h_mal/appttude/com/driver/objects/ArchiveObject.kt +++ b/app/src/admin/java/h_mal/appttude/com/driver/objects/ArchiveObject.kt @@ -1,33 +1,20 @@ -package h_mal.appttude.com.driver.admin.objects +package h_mal.appttude.com.driver.objects -import h_mal.appttude.com.driver.model.* -import java.util.* +import h_mal.appttude.com.driver.model.DriversLicense +import h_mal.appttude.com.driver.model.Insurance +import h_mal.appttude.com.driver.model.Logbook +import h_mal.appttude.com.driver.model.Mot +import h_mal.appttude.com.driver.model.PrivateHireLicense +import h_mal.appttude.com.driver.model.PrivateHireVehicle +import h_mal.appttude.com.driver.model.VehicleProfile -class ArchiveObject { - var driver_license: HashMap? = null - var private_hire: HashMap? = null - var vehicle_details: HashMap? = null - var insurance_details: HashMap? = null - var mot_details: HashMap? = null - var log_book: HashMap? = null - var ph_car: HashMap? = null - constructor() - constructor( - driver_license: HashMap?, - private_hire: HashMap?, - vehicle_details: HashMap?, - insurance_details: HashMap?, - mot_details: HashMap?, - log_book: HashMap?, - private_hire_vehicle: HashMap? - ) { - 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 - } -} \ No newline at end of file +data class ArchiveObject( + var driver_license: HashMap? = HashMap(), + var private_hire: HashMap? = HashMap(), + var vehicle_details: HashMap? = HashMap(), + var insurance_details: HashMap? = HashMap(), + var mot_details: HashMap? = HashMap(), + var log_book: HashMap? = HashMap(), + var ph_car: HashMap? = HashMap(), +) \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/objects/UserObject.kt b/app/src/admin/java/h_mal/appttude/com/driver/objects/UserObject.kt index 918d377..a2c3c12 100644 --- a/app/src/admin/java/h_mal/appttude/com/driver/objects/UserObject.kt +++ b/app/src/admin/java/h_mal/appttude/com/driver/objects/UserObject.kt @@ -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 - } -} \ No newline at end of file +@IgnoreExtraProperties +data class UserObject ( + var profileName: String? = "", + var profileEmail: String? = "", + var profilePicString: String? = "", +) \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/objects/WholeDriverObject.kt b/app/src/admin/java/h_mal/appttude/com/driver/objects/WholeDriverObject.kt index 727e872..dd68623 100644 --- a/app/src/admin/java/h_mal/appttude/com/driver/objects/WholeDriverObject.kt +++ b/app/src/admin/java/h_mal/appttude/com/driver/objects/WholeDriverObject.kt @@ -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() -} \ No newline at end of file +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? = "", +) \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/DriverProfile.kt b/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/DriverProfile.kt index 36ffc4b..26f47ee 100644 --- a/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/DriverProfile.kt +++ b/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/DriverProfile.kt @@ -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() -} \ No newline at end of file +data class DriverProfile( + var driver_profile: DriverProfile? = DriverProfile(), + var driver_license: DriversLicense? = DriversLicense(), + var private_hire: PrivateHireLicense? = PrivateHireLicense(), +) \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/MappedObject.kt b/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/MappedObject.kt deleted file mode 100644 index 20649ed..0000000 --- a/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/MappedObject.kt +++ /dev/null @@ -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 = - object : Parcelable.Creator { - override fun createFromParcel(`in`: Parcel): MappedObject? { - return MappedObject(`in`) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } - } -} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/VehicleProfile.kt b/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/VehicleProfile.kt index e2f3a26..1828463 100644 --- a/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/VehicleProfile.kt +++ b/app/src/admin/java/h_mal/appttude/com/driver/objects/wholeObject/VehicleProfile.kt @@ -1,31 +1,15 @@ -package h_mal.appttude.com.driver.admin.objects.wholeObject +package h_mal.appttude.com.driver.objects.wholeObject import h_mal.appttude.com.driver.model.Insurance import h_mal.appttude.com.driver.model.Logbook -import h_mal.appttude.com.driver.model.PrivateHireVehicle import h_mal.appttude.com.driver.model.Mot +import h_mal.appttude.com.driver.model.PrivateHireVehicle import h_mal.appttude.com.driver.model.VehicleProfile - -class VehicleProfile { - var insurance_details: Insurance? = null - var log_book: Logbook? = null - var mot_details: Mot? = null - var vehicle_details: VehicleProfile? = null - var privateHireVehicle: PrivateHireVehicle? = null - - constructor() - constructor( - insurance_details: Insurance?, - log_book: Logbook?, - mot_details: Mot?, - vehicle_details: VehicleProfile?, - private_hire_vehicle: PrivateHireVehicle? - ) { - this.insurance_details = insurance_details - this.log_book = log_book - this.mot_details = mot_details - this.vehicle_details = vehicle_details - this.privateHireVehicle = private_hire_vehicle - } -} \ No newline at end of file +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() +) \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/ApprovalListAdapter.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/ApprovalListAdapter.kt index 7456f16..297f572 100644 --- a/app/src/admin/java/h_mal/appttude/com/driver/ui/ApprovalListAdapter.kt +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/ApprovalListAdapter.kt @@ -1,188 +1,57 @@ package h_mal.appttude.com.driver.ui -import android.app.Activity +import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter -import h_mal.appttude.com.driver.admin.objects.wholeObject.MappedObject -import h_mal.appttude.com.driver.R +import h_mal.appttude.com.driver.databinding.ApprovalListItemBinding +import h_mal.appttude.com.driver.model.ApprovalStatus +import h_mal.appttude.com.driver.utils.hide +import java.io.IOException class ApprovalListAdapter( - private val activity: Activity, - objects: Array -) : ArrayAdapter(activity, 0, objects) { + private val context: Context, + private val data: List>, + private val callback: (String) -> Unit +) : ArrayAdapter>(context, 0, data) { - var mappedObject: MappedObject? = objects[0] + override fun getCount(): Int = data.size - var names: Array = 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 - ) + // Inflate view binding into listview cell + binding = ApprovalListItemBinding.inflate(LayoutInflater.from(context), parent, false) + listItemView = binding.root + listItemView.setTag(listItemView.id, binding) + } else { + // cell exists so recycling view + binding = listItemView.getTag(listItemView.id) as ApprovalListItemBinding } -// 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)!! + + val key: String = getItem(position)?.first ?: throw IOException("No document name provided") + val approvalStatus: ApprovalStatus? = getItem(position)?.second + + binding.approvalText.text = key + approvalStatus?.let { item -> + item.score.takeIf { it != 0 }?.let { + binding.root.setOnClickListener { callback.invoke(key) } + } + binding.approvalIv.setImageResource(item.drawableId) + binding.approvalStatus.text = context.getString(item.stringId) + } + // hide divider for first cell + if (position == 0) binding.divider.hide() + + return (listItemView) } -// 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) -// } -// } - + fun updateAdapter(date: List>) { + clear() + addAll(date) + } } \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/ApproverFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/ApproverFragment.kt new file mode 100644 index 0000000..16df2d8 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/ApproverFragment.kt @@ -0,0 +1,39 @@ +package h_mal.appttude.com.driver.ui + +import h_mal.appttude.com.driver.base.BaseFragment +import h_mal.appttude.com.driver.databinding.FragmentApproverBinding +import h_mal.appttude.com.driver.model.ApprovalStatus +import h_mal.appttude.com.driver.viewmodels.ApproverViewModel + + +class ApproverFragment : BaseFragment() { + + override fun setupView(binding: FragmentApproverBinding) = binding.run { + super.setupView(binding) + + val args = requireArguments() + viewModel.init(args) + + // Retrieve fragment name argument saved from previous fragment + val fragmentClass = viewModel.getFragmentClass() + + childFragmentManager.beginTransaction() + .replace(container.id, fragmentClass, args, null) + .commitNow() + + approve.setOnClickListener { viewModel.approveDocument() } + decline.setOnClickListener { viewModel.declineDocument() } + } + + override fun onSuccess(data: Any?) { + super.onSuccess(data) + when (data) { + ApprovalStatus.APPROVED -> displaySnackBar("approved") + ApprovalStatus.DENIED -> displaySnackBar("declined") + } + } + + private fun displaySnackBar(status: String) { + showSnackBar("Document has been $status") + } +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/DriverOverviewFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/DriverOverviewFragment.kt new file mode 100644 index 0000000..3648f15 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/DriverOverviewFragment.kt @@ -0,0 +1,54 @@ +package h_mal.appttude.com.driver.ui + +import android.widget.ListView +import h_mal.appttude.com.driver.R +import h_mal.appttude.com.driver.base.BaseFragment +import h_mal.appttude.com.driver.data.USER_CONST +import h_mal.appttude.com.driver.databinding.FragmentUserMainBinding +import h_mal.appttude.com.driver.model.ApprovalStatus +import h_mal.appttude.com.driver.utils.FRAGMENT +import h_mal.appttude.com.driver.utils.navigateTo +import h_mal.appttude.com.driver.utils.toBundle +import h_mal.appttude.com.driver.viewmodels.DriverOverviewViewModel +import java.io.IOException + + +class DriverOverviewFragment : BaseFragment() { + + private lateinit var listView: ListView + private lateinit var driverId: String + + override fun setupView(binding: FragmentUserMainBinding) { + listView = binding.approvalsList + loadList() + } + + override fun onResume() { + super.onResume() + loadList() + } + + override fun onSuccess(data: Any?) { + super.onSuccess(data) + @Suppress("UNCHECKED_CAST") + if (data is List<*>) { + val listData = data as List> + if (listView.adapter == null) { + listView.adapter = ApprovalListAdapter(requireContext(), listData) { + this.view?.navigateTo( + R.id.to_approverFragment, + driverId.toBundle(USER_CONST).apply { putString(FRAGMENT, it) }) + } + listView.isScrollContainer = false + } else { + (listView.adapter as ApprovalListAdapter).updateAdapter(listData) + } + } + } + + private fun loadList() { + driverId = requireArguments().getString(USER_CONST) + ?: throw IOException("No user ID has been passed") + viewModel.loadDriverApprovals(driverId) + } +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/DriverStatusClass.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/DriverStatusClass.kt deleted file mode 100644 index ada9224..0000000 --- a/app/src/admin/java/h_mal/appttude/com/driver/ui/DriverStatusClass.kt +++ /dev/null @@ -1,107 +0,0 @@ -package h_mal.appttude.com.driver.ui - -import android.app.AlertDialog -import android.content.Context -import android.view.View -import androidx.cardview.widget.CardView - - -class DriverStatusClass : View.OnClickListener { - var userId: String? = null - var cardView: CardView? = null - var context: Context? = null - var currentSelection: Boolean = false - override fun onClick(v: View) { - val choices: Array = arrayOf("Active", "Inactive") - val alertDialog: AlertDialog.Builder = AlertDialog.Builder(context) - var selection: Int = -1 - if (currentSelection) { - selection = 0 - } else if (!currentSelection) { - selection = 1 - } - alertDialog.setSingleChoiceItems( - choices, - selection - ) { dialog, which -> } - alertDialog.create().show() - } - - private fun SetStatus(status: Boolean) { -// MainActivity.mDatabase!!.child(FirebaseClass.USER_FIREBASE) -// .child((userId)!!).child(FirebaseClass.DRIVER_STATUS).setValue(status) -// .addOnCompleteListener { task -> -// if (task.isSuccessful) { -// cardView!!.setBackgroundColor(setStatusColour(status)) -// } else { -// } -// } - } - - private fun setStatusColour(b: Boolean): Int { - if (b) { - return android.R.color.holo_green_dark - } else { - return android.R.color.holo_red_dark - } - } // public int getOverApprovalStatusCode(WholeDriverObject wholeDriverObject){ - // - // if (wholeDriverObject.approvalsObject != null){ - // ApprovalsObject approvalsObject = wholeDriverObject.approvalsObject; - // - // int[] ints = new int[]{approvalsObject.getDriver_details_approval(), - // approvalsObject.driver_license_approval, - // approvalsObject.private_hire_approval, - // approvalsObject.vehicle_details_approval, - // approvalsObject.insurance_details_approval, - // approvalsObject.mot_details_approval, - // approvalsObject.log_book_approval, - // approvalsObject.ph_car_approval}; - // - // - // return setImageResource(mode(ints)); - // } - // - // return setImageResource(NO_DATE_PRESENT); - // } - // - // public void setStatusCode(String userId,String approvalNameString,int status){ - // - // if (!approvalNameString.equals("")) { - // mDatabase.child(USER_FIREBASE).child(userId).child(USER_APPROVALS).child(approvalNameString) - // .setValue(status).addOnCompleteListener(new OnCompleteListener() { - // @Override - // public void onComplete(@NonNull Task task) { - // if (task.isSuccessful()) { - // - // } else { - // - // } - // } - // }); - // } - // - // - // } - // - // public int setImageResource(int statusCode){ - // int imageResource; - // - // switch (statusCode){ - // case APPROVAL_PENDING: - // imageResource = R.drawable.pending; - // break; - // case APPROVAL_DENIED: - // imageResource = R.drawable.denied; - // break; - // case APPROVED: - // imageResource = R.drawable.approved; - // break; - // default: - // imageResource = R.drawable.zero; - // break; - // } - // - // return imageResource; - // } -} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/HomeSuperUserFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/HomeSuperUserFragment.kt index ea24f2c..7b0e28f 100644 --- a/app/src/admin/java/h_mal/appttude/com/driver/ui/HomeSuperUserFragment.kt +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/HomeSuperUserFragment.kt @@ -1,153 +1,208 @@ package h_mal.appttude.com.driver.ui import android.app.AlertDialog -import android.content.DialogInterface -import android.content.SharedPreferences import android.os.Bundle -import android.util.Log import android.view.* -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView +import android.widget.EditText +import android.widget.LinearLayout +import androidx.core.view.MenuProvider +import androidx.core.widget.doOnTextChanged +import androidx.lifecycle.Lifecycle +import com.firebase.ui.common.ChangeEventType +import com.firebase.ui.database.FirebaseRecyclerAdapter +import com.firebase.ui.database.FirebaseRecyclerOptions import com.google.firebase.database.DataSnapshot -import com.google.firebase.database.DatabaseError -import com.google.firebase.database.DatabaseReference -import com.google.firebase.database.ValueEventListener -import h_mal.appttude.com.driver.admin.objects.WholeDriverObject -import h_mal.appttude.com.driver.admin.objects.wholeObject.MappedObject import h_mal.appttude.com.driver.R -import java.io.IOException +import h_mal.appttude.com.driver.base.BaseFirebaseAdapter +import h_mal.appttude.com.driver.base.BaseFragment +import h_mal.appttude.com.driver.base.CustomViewHolder +import h_mal.appttude.com.driver.data.USER_CONST +import h_mal.appttude.com.driver.databinding.FragmentHomeSuperUserBinding +import h_mal.appttude.com.driver.databinding.ListItemLayoutBinding +import h_mal.appttude.com.driver.model.DatabaseStatus +import h_mal.appttude.com.driver.model.DatabaseStatus.* +import h_mal.appttude.com.driver.model.SortOption +import h_mal.appttude.com.driver.objects.UserObject +import h_mal.appttude.com.driver.objects.WholeDriverObject +import h_mal.appttude.com.driver.utils.* +import h_mal.appttude.com.driver.viewmodels.SuperUserViewModel import java.util.* -class HomeSuperUserFragment : Fragment() { - var users: DatabaseReference? = null - var mappedObjectList: MutableList? = 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(), + MenuProvider { + private lateinit var adapter: FirebaseRecyclerAdapter> + + 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(R.id.recycler_view).apply { - layoutManager = LinearLayoutManager(context) - recyclerViewAdapter = RecyclerViewAdapter(context, mappedObjectList) - adapter = recyclerViewAdapter + override fun onSuccess(data: Any?) { + super.onSuccess(data) + when (data) { + is FirebaseRecyclerOptions<*> -> setAdapterToRecyclerView(data) + } + } + private fun setNonView(status: DatabaseStatus) { + applyBinding { + emptyView.run { + root.setOnClickListener(null) + root.visibility = View.VISIBLE + icon.setImageResource(status.drawable) + header.setText(status.header) + subtext.setText(status.subtext) + } } - - return view } - 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 - ) + @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) + recyclerView.adapter = adapter + recyclerView.setHasFixedSize(true) + adapter.startListening() + } else { + adapter.updateOptions(options as FirebaseRecyclerOptions) + } + } + } + + override fun onStart() { + super.onStart() + applyBinding { + if (recyclerView.adapter != null) { + adapter.startListening() + } + } + } + + override fun onStop() { + super.onStop() + adapter.stopListening() + } + + private fun createAdapter(options: FirebaseRecyclerOptions): BaseFirebaseAdapter { + return object : + BaseFirebaseAdapter(options, layoutInflater) { + override fun onBindViewHolder( + holder: CustomViewHolder, + 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 onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.menu_calls_fragment, menu) - super.onCreateOptionsMenu(menu, inflater) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == R.id.archive) { - val grpname: Array = arrayOf("Driver Name", "Driver Number", "Approval") - sortOrder = sharedPreferences!!.getInt(SORT, 0) - val checkedItem: Int = sortOrder - var compareInt = 0 - val click = DialogInterface.OnClickListener { dialog, _ -> - sortDate(compareInt, false) - dialog.dismiss() - } - val builder: AlertDialog.Builder = AlertDialog.Builder(context) - builder.setTitle("Sort by:") - .setSingleChoiceItems( - grpname, - checkedItem - ) { _, pos -> compareInt = pos } - .setPositiveButton("Ascending", click) - .setNegativeButton("Descending", click) - .create().show() - } - return super.onOptionsItemSelected(item) - } - - private fun sortDate(compareInt: Int, reversed: Boolean) { - val comparator: Comparator = object : Comparator { - override fun compare(o1: MappedObject, o2: MappedObject): Int { - when (compareInt) { - 0 -> return o1.wholeDriverObject?.user_details?.profileName!!.compareTo( - o2.wholeDriverObject?.user_details?.profileName!! - ) - 1 -> { - var s1: String? = o1.wholeDriverObject?.driver_number - var s2: String? = o2.wholeDriverObject?.driver_number - if (o1.wholeDriverObject?.driver_number == null || (o1.wholeDriverObject!! - .driver_number == "0") - ) { - s1 = ";" - } - if (o2.wholeDriverObject?.driver_number == null || (o2.wholeDriverObject!! - .driver_number == "0") - ) { - s2 = ";" - } - return s1!!.compareTo((s2)!!) - } - else -> { - throw IOException("dfdfs") - } -// 2 -> return MainActivity.approvalsClass.getOverApprovalStatusCode(o1.wholeDriverObject) - -// MainActivity.approvalsClass.getOverApprovalStatusCode(o2.wholeDriverObject) -// else -> return MainActivity.approvalsClass.getOverApprovalStatusCode( -// o1.wholeDriverObject -// ) - MainActivity.approvalsClass.getOverApprovalStatusCode(o2.wholeDriverObject) + override fun onDataChanged() { + super.onDataChanged() + applyBinding { + // If there are no driver data, show a view that informs the admin. + emptyView.root.visibility = if (itemCount == 0) View.VISIBLE else View.GONE + progressCircular.hide() } } + + override fun onChildChanged( + type: ChangeEventType, + snapshot: DataSnapshot, + newIndex: Int, + oldIndex: Int + ) { + super.onChildChanged(type, snapshot, newIndex, oldIndex) + applyBinding { progressCircular.hide() } + } + + override fun authorizationError() { + setNonView(NO_AUTHORIZATION) + } + + override fun cannotRetrieve() { + setNonView(CANNOT_RETRIEVE) + } + + override fun noConnection() { + setNonView(NO_CONNECTION) + } + + override fun permissionsDenied() { + setNonView(NO_PERMISSION) + } + + override fun emptyList() { + setNonView(EMPTY_RESULTS) + } } - sharedPreferences!!.edit().putInt(SORT, compareInt).apply() - sharedPreferences!!.edit().putBoolean(REVERSED, reversed).apply() - if (reversed) { - Collections.sort(mappedObjectList, comparator.reversed()) - } else { - Collections.sort(mappedObjectList, comparator) - } - recyclerViewAdapter!!.notifyDataSetChanged() } - companion object { - private val SORT: String = "SORT" - private val REVERSED: String = "REVERSED" + private fun showChangeNumberDialog(defaultNumber: String, uid: String) { + val inputText = EditText(context).apply { + setTag(R.string.driver_identifier, "DriverIdentifierInput") + setText(defaultNumber) + setSelectAllOnFocus(true) + doOnTextChanged { _, _, count, _ -> if (count > 6) showToast("Identifier cannot be larger than 6") } + } + val layout = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + setPadding(28, 0, 56, 0) + addView(inputText) + } + + AlertDialog.Builder(requireContext(), R.style.AppTheme_AppBarOverlay) + .setTitle("Change Driver Identifier") + .setView(layout) + .setPositiveButton("Submit") { _, _ -> + val input = inputText.text?.toString() + input?.let { viewModel.updateDriverNumber(uid, it) } + }.create().show() + } + + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.menu_calls_fragment, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (menuItem.itemId == R.id.archive) { + displaySortOptions() + } + return true + } + + private fun displaySortOptions() { + val groupName: Array = arrayOf("Driver Name", "Driver Number") + val defaultPosition = viewModel.getSelectionAsPosition() + val builder: AlertDialog.Builder = AlertDialog.Builder(context) + builder.setTitle("Sort by:") + .setSingleChoiceItems( + groupName, + defaultPosition + ) { _, pos -> + val option = SortOption.getSortOptionByLabel(groupName[pos]) + viewModel.createFirebaseOptions(sort = option) + } + .create().show() } } \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/RecyclerViewAdapter.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/RecyclerViewAdapter.kt deleted file mode 100644 index e545e4f..0000000 --- a/app/src/admin/java/h_mal/appttude/com/driver/ui/RecyclerViewAdapter.kt +++ /dev/null @@ -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?) : - RecyclerView.Adapter() { - 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) - } - } -} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/UserMainFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/UserMainFragment.kt deleted file mode 100644 index ada4bdd..0000000 --- a/app/src/admin/java/h_mal/appttude/com/driver/ui/UserMainFragment.kt +++ /dev/null @@ -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("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 - } -} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/driverprofile/DriverLicenseFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/driverprofile/DriverLicenseFragment.kt new file mode 100644 index 0000000..de8542a --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/driverprofile/DriverLicenseFragment.kt @@ -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() { + + 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) + } + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/driverprofile/DriverProfileFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/driverprofile/DriverProfileFragment.kt new file mode 100644 index 0000000..2dc8b81 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/driverprofile/DriverProfileFragment.kt @@ -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() { + + 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) + } + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/driverprofile/PrivateHireLicenseFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/driverprofile/PrivateHireLicenseFragment.kt new file mode 100644 index 0000000..8fce996 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/driverprofile/PrivateHireLicenseFragment.kt @@ -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() { + + 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) + } + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/InsuranceFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/InsuranceFragment.kt new file mode 100644 index 0000000..12cf1f4 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/InsuranceFragment.kt @@ -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() { + + override fun setupView(binding: FragmentInsuranceBinding) { + super.setupView(binding) + viewsToHide(binding.submit, binding.uploadInsurance) + } + + + private fun updateImageCarousal(list: List) { + 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) } + } + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/LogbookFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/LogbookFragment.kt new file mode 100644 index 0000000..679ddb9 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/LogbookFragment.kt @@ -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() { + + 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) + } + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/MotFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/MotFragment.kt new file mode 100644 index 0000000..1c0a744 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/MotFragment.kt @@ -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() { + + 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) + } + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/PrivateHireVehicleFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/PrivateHireVehicleFragment.kt new file mode 100644 index 0000000..b64e84d --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/PrivateHireVehicleFragment.kt @@ -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() { + + 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) + } + + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/VehicleProfileFragment.kt b/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/VehicleProfileFragment.kt new file mode 100644 index 0000000..309cab7 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/ui/vehicleprofile/VehicleProfileFragment.kt @@ -0,0 +1,32 @@ +package h_mal.appttude.com.driver.ui.vehicleprofile + +import h_mal.appttude.com.driver.base.DataViewerFragment +import h_mal.appttude.com.driver.databinding.FragmentVehicleSetupBinding +import h_mal.appttude.com.driver.model.VehicleProfile +import h_mal.appttude.com.driver.viewmodels.VehicleProfileViewModel + + +class VehicleProfileFragment : + DataViewerFragment() { + + override fun setupView(binding: FragmentVehicleSetupBinding) { + super.setupView(binding) + viewsToHide(binding.submit) + binding.seizedCheckbox.isEnabled = false + } + + override fun setFields(data: VehicleProfile) { + super.setFields(data) + applyBinding { + reg.setText(data.reg) + make.setText(data.make) + carModel.setText(data.model) + colour.setText(data.colour) + keeperName.setText(data.keeperName) + address.setText(data.keeperAddress) + postcode.setText(data.keeperPostCode) + startDate.setText(data.startDate) + seizedCheckbox.isChecked = data.isSeized + } + } +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/utils/Constants.kt b/app/src/admin/java/h_mal/appttude/com/driver/utils/Constants.kt new file mode 100644 index 0000000..09d4953 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/utils/Constants.kt @@ -0,0 +1,3 @@ +package h_mal.appttude.com.driver.utils + +const val FRAGMENT = "fragment" \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/ApproverViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/ApproverViewModel.kt new file mode 100644 index 0000000..d31570b --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/ApproverViewModel.kt @@ -0,0 +1,104 @@ +package h_mal.appttude.com.driver.viewmodels + +import android.content.res.Resources +import android.os.Bundle +import androidx.fragment.app.Fragment +import com.google.firebase.database.DatabaseReference +import h_mal.appttude.com.driver.R +import h_mal.appttude.com.driver.base.BaseViewModel +import h_mal.appttude.com.driver.data.FirebaseCompletion +import h_mal.appttude.com.driver.data.FirebaseDatabaseSource +import h_mal.appttude.com.driver.data.USER_CONST +import h_mal.appttude.com.driver.model.ApprovalStatus +import h_mal.appttude.com.driver.objects.ApprovalsObject +import h_mal.appttude.com.driver.ui.driverprofile.DriverLicenseFragment +import h_mal.appttude.com.driver.ui.driverprofile.DriverProfileFragment +import h_mal.appttude.com.driver.ui.driverprofile.PrivateHireLicenseFragment +import h_mal.appttude.com.driver.ui.vehicleprofile.InsuranceFragment +import h_mal.appttude.com.driver.ui.vehicleprofile.LogbookFragment +import h_mal.appttude.com.driver.ui.vehicleprofile.MotFragment +import h_mal.appttude.com.driver.ui.vehicleprofile.PrivateHireVehicleFragment +import h_mal.appttude.com.driver.ui.vehicleprofile.VehicleProfileFragment +import h_mal.appttude.com.driver.utils.Coroutines.io +import h_mal.appttude.com.driver.utils.FRAGMENT +import h_mal.appttude.com.driver.utils.getDataFromDatabaseRef + +class ApproverViewModel( + private val resources: Resources, + private val database: FirebaseDatabaseSource +) : BaseViewModel() { + + private lateinit var name: String + private lateinit var docRef: DatabaseReference + + private var score: ApprovalStatus? = null + + fun init(args: Bundle) { + // Retried uid & fragment class name from args + val uid = args.getString(USER_CONST) ?: throw NullPointerException("No user Id was passed") + name = args.getString(FRAGMENT) + ?: throw NullPointerException("No fragment name argument passed") + // Define a document name based on fragment class name + val documentName = when (name) { + resources.getString(R.string.driver_profile) -> ApprovalsObject::driver_details_approval.name + resources.getString(R.string.drivers_license) -> ApprovalsObject::driver_license_approval.name + resources.getString(R.string.private_hire_license) -> ApprovalsObject::private_hire_approval.name + resources.getString(R.string.vehicle_profile) -> ApprovalsObject::vehicle_details_approval.name + resources.getString(R.string.insurance) -> ApprovalsObject::insurance_details_approval.name + resources.getString(R.string.m_o_t) -> ApprovalsObject::mot_details_approval.name + resources.getString(R.string.log_book) -> ApprovalsObject::log_book_approval.name + resources.getString(R.string.private_hire_vehicle_license) -> ApprovalsObject::ph_car_approval.name + else -> { + throw StringIndexOutOfBoundsException("No resource for $name") + } + } + + docRef = database.getDocumentApprovalRef(uid, documentName) + io { + doTryOperation("") { + val data = docRef.getDataFromDatabaseRef() + score = data?.let { ApprovalStatus.getByScore(it) } ?: ApprovalStatus.NOT_SUBMITTED + onSuccess(FirebaseCompletion.Default) + } + } + } + + fun getFragmentClass(): Class { + return when (name) { + resources.getString(R.string.driver_profile) -> DriverProfileFragment::class.java + resources.getString(R.string.drivers_license) -> DriverLicenseFragment::class.java + resources.getString(R.string.private_hire_license) -> PrivateHireLicenseFragment::class.java + resources.getString(R.string.vehicle_profile) -> VehicleProfileFragment::class.java + resources.getString(R.string.insurance) -> InsuranceFragment::class.java + resources.getString(R.string.m_o_t) -> MotFragment::class.java + resources.getString(R.string.log_book) -> LogbookFragment::class.java + resources.getString(R.string.private_hire_vehicle_license) -> PrivateHireVehicleFragment::class.java + else -> { + throw StringIndexOutOfBoundsException("No resource for $name") + } + } + } + + fun approveDocument() { + updateDocument(ApprovalStatus.APPROVED) + } + + fun declineDocument() { + updateDocument(ApprovalStatus.DENIED) + } + + private fun updateDocument(approval: ApprovalStatus) { + if (approval == score) { + val result = if (approval == ApprovalStatus.APPROVED) "approved" else "declined" + onError("Document already $result") + return + } + + io { + doTryOperation("Failed to decline document") { + database.postToDatabaseRed(docRef, approval.score) + onSuccess(approval) + } + } + } +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/DriverLicenseViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/DriverLicenseViewModel.kt new file mode 100644 index 0000000..86725a8 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/DriverLicenseViewModel.kt @@ -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() { + + override fun getDatabaseRef(uid: String) = database.getDriverLicenseRef(uid) +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/DriverOverviewViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/DriverOverviewViewModel.kt new file mode 100644 index 0000000..fbda4ad --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/DriverOverviewViewModel.kt @@ -0,0 +1,61 @@ +package h_mal.appttude.com.driver.viewmodels + +import com.google.firebase.database.DatabaseReference +import com.google.firebase.storage.StorageReference +import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel +import h_mal.appttude.com.driver.data.FirebaseAuthentication +import h_mal.appttude.com.driver.data.FirebaseDatabaseSource +import h_mal.appttude.com.driver.model.ApprovalStatus +import h_mal.appttude.com.driver.objects.ApprovalsObject +import h_mal.appttude.com.driver.utils.Coroutines.io +import h_mal.appttude.com.driver.utils.getDataFromDatabaseRef +import kotlinx.coroutines.Job +import java.io.IOException + +class DriverOverviewViewModel( + auth: FirebaseAuthentication, + private val database: FirebaseDatabaseSource +) : DataSubmissionBaseViewModel(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() + + fun loadDriverApprovals(driverId: String?) { + this.driverId = driverId + io { + doTryOperation("Failed to retrieve $objectName") { + val data = driverId?.let { + database.getApprovalsRef(it).getDataFromDatabaseRef() + } ?: ApprovalsObject() + val mappedData = mapApprovalsForView(data) + onSuccess(mappedData) + return@doTryOperation + } + } + } + + private fun mapApprovalsForView(data: ApprovalsObject): List> { + val list = mutableListOf>() + return list.apply { + add(0, Pair("Driver Profile", getApprovalStatusByScore(data.driver_details_approval))) + add(1, Pair("Drivers License", getApprovalStatusByScore(data.driver_license_approval))) + add(2, Pair("Private Hire License", getApprovalStatusByScore(data.private_hire_approval))) + add(3, Pair("Vehicle Profile", getApprovalStatusByScore(data.vehicle_details_approval))) + add(4, Pair("Insurance", getApprovalStatusByScore(data.insurance_details_approval))) + add(5, Pair("M.O.T", getApprovalStatusByScore(data.mot_details_approval))) + add(6, Pair("Log book", getApprovalStatusByScore(data.log_book_approval))) + add(7, Pair("Private Hire Vehicle License", getApprovalStatusByScore(data.ph_car_approval))) + } + } + + private fun getApprovalStatusByScore(score: Int): ApprovalStatus { + if (score == 0) return ApprovalStatus.NOT_SUBMITTED + return ApprovalStatus.getByScore(score) ?: throw IOException("No approval for score $score") + } + +} diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/DriverProfileViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/DriverProfileViewModel.kt new file mode 100644 index 0000000..4b0c6c4 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/DriverProfileViewModel.kt @@ -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() { + override fun getDatabaseRef(uid: String) = database.getDriverDetailsRef(uid) +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/InsuranceViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/InsuranceViewModel.kt new file mode 100644 index 0000000..6aed36d --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/InsuranceViewModel.kt @@ -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() { + + override fun getDatabaseRef(uid: String) = database.getInsuranceDetailsRef(uid) +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/LogbookViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/LogbookViewModel.kt new file mode 100644 index 0000000..2bad2c1 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/LogbookViewModel.kt @@ -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() { + + override fun getDatabaseRef(uid: String) = database.getLogbookRef(uid) +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/MotViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/MotViewModel.kt new file mode 100644 index 0000000..3143066 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/MotViewModel.kt @@ -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() { + + override fun getDatabaseRef(uid: String) = database.getMotDetailsRef(uid) +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/PrivateHireLicenseViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/PrivateHireLicenseViewModel.kt new file mode 100644 index 0000000..73cc556 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/PrivateHireLicenseViewModel.kt @@ -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() { + + override fun getDatabaseRef(uid: String) = database.getPrivateHireRef(uid) +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/PrivateHireVehicleViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/PrivateHireVehicleViewModel.kt new file mode 100644 index 0000000..1716d75 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/PrivateHireVehicleViewModel.kt @@ -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() { + + override fun getDatabaseRef(uid: String) = database.getPrivateHireVehicleRef(uid) +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/SuperUserViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/SuperUserViewModel.kt new file mode 100644 index 0000000..9fdda57 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/SuperUserViewModel.kt @@ -0,0 +1,65 @@ +package h_mal.appttude.com.driver.viewmodels + +import com.firebase.ui.database.FirebaseRecyclerOptions +import h_mal.appttude.com.driver.base.BaseViewModel +import h_mal.appttude.com.driver.data.FirebaseDatabaseSource +import h_mal.appttude.com.driver.data.prefs.PreferenceProvider +import h_mal.appttude.com.driver.model.SortOption +import h_mal.appttude.com.driver.objects.WholeDriverObject +import h_mal.appttude.com.driver.utils.Coroutines.io +import h_mal.appttude.com.driver.utils.isNotNull + + +class SuperUserViewModel( + private val firebaseDatabaseSource: FirebaseDatabaseSource, + private val preferenceProvider: PreferenceProvider +) : BaseViewModel() { + + fun retrieveDefaultFirebaseOptions() { + val optionLabel = preferenceProvider.getSortOption() + val option = SortOption.getSortOptionByLabel(optionLabel) + createFirebaseOptions(option) + } + + fun createFirebaseOptions(sort: SortOption? = null) { + val ref = firebaseDatabaseSource.getUsersRef().orderByChild("role").startAt("driver").endAt("driver") + + sort?.isNotNull { preferenceProvider.setSortOption(it.label) } + +// val query = when(sort) { +// NAME, NUMBER -> ref.orderByChild(sort.key) +//// SortOption.APPROVAL -> TODO() +// else -> ref.orderByKey() +// } + + val options = FirebaseRecyclerOptions.Builder() + .setQuery(ref, WholeDriverObject::class.java) + .build() + + onSuccess(options) + } + + fun updateDriverNumber(uid: String, input: String?) { + io { + doTryOperation("failed update driver identifier") { + // validate input + if (input.isNullOrBlank()) { + onError("No driver identifier provided") + return@doTryOperation + } + val text = if (input.length > 6) input.substring(0, 7) else input + + firebaseDatabaseSource.run { + postToDatabaseRed(getDriverNumberRef(uid), text) + onSuccess(Unit) + } + } + } + } + + fun getSelectionAsPosition(): Int { + val optionLabel = preferenceProvider.getSortOption() + return SortOption.getPositionByLabel(optionLabel) ?: -1 + } + +} \ No newline at end of file diff --git a/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/VehicleProfileViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/VehicleProfileViewModel.kt new file mode 100644 index 0000000..ca13476 --- /dev/null +++ b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/VehicleProfileViewModel.kt @@ -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() { + + override fun getDatabaseRef(uid: String) = database.getVehicleDetailsRef(uid) +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/admin/res/layout/activity_main.xml similarity index 100% rename from app/src/main/res/layout/activity_main.xml rename to app/src/admin/res/layout/activity_main.xml diff --git a/app/src/admin/res/layout/approval_list_grid_item.xml b/app/src/admin/res/layout/approval_list_grid_item.xml deleted file mode 100644 index 3eef1e4..0000000 --- a/app/src/admin/res/layout/approval_list_grid_item.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/admin/res/layout/approval_list_item.xml b/app/src/admin/res/layout/approval_list_item.xml new file mode 100644 index 0000000..9b9be7e --- /dev/null +++ b/app/src/admin/res/layout/approval_list_item.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/admin/res/layout/empty_layout.xml b/app/src/admin/res/layout/empty_layout.xml new file mode 100644 index 0000000..7894b2e --- /dev/null +++ b/app/src/admin/res/layout/empty_layout.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/admin/res/layout/empty_users_view.xml b/app/src/admin/res/layout/empty_users_view.xml new file mode 100644 index 0000000..d830efc --- /dev/null +++ b/app/src/admin/res/layout/empty_users_view.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/admin/res/layout/fragment_approver.xml b/app/src/admin/res/layout/fragment_approver.xml new file mode 100644 index 0000000..71d5ae4 --- /dev/null +++ b/app/src/admin/res/layout/fragment_approver.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/admin/res/layout/fragment_home_super_user.xml b/app/src/admin/res/layout/fragment_home_super_user.xml index dcf94cf..9a8e8a7 100644 --- a/app/src/admin/res/layout/fragment_home_super_user.xml +++ b/app/src/admin/res/layout/fragment_home_super_user.xml @@ -1,16 +1,42 @@ - - - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/app/src/admin/res/layout/fragment_user_main.xml b/app/src/admin/res/layout/fragment_user_main.xml index c2ea597..c7cb829 100644 --- a/app/src/admin/res/layout/fragment_user_main.xml +++ b/app/src/admin/res/layout/fragment_user_main.xml @@ -1,17 +1,32 @@ - + tools:context=".ui.DriverOverviewFragment"> - + 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"> - \ No newline at end of file + + + + \ No newline at end of file diff --git a/app/src/admin/res/layout/list_item_layout.xml b/app/src/admin/res/layout/list_item_layout.xml index c7446e5..7f935f3 100644 --- a/app/src/admin/res/layout/list_item_layout.xml +++ b/app/src/admin/res/layout/list_item_layout.xml @@ -10,7 +10,7 @@ android:id="@+id/driverPic" android:layout_width="50dp" android:layout_height="50dp" - android:layout_marginLeft="24dp" + android:layout_marginStart="24dp" android:layout_marginTop="24dp" android:layout_marginBottom="24dp" android:adjustViewBounds="true" @@ -26,18 +26,19 @@ android:id="@+id/approval_iv" android:layout_width="10dp" android:layout_height="10dp" - android:layout_marginRight="3dp" + android:layout_marginEnd="3dp" android:layout_marginTop="3dp" app:layout_constraintTop_toTopOf="@id/driverPic" app:layout_constraintRight_toRightOf="@id/driverPic" android:adjustViewBounds="true" - tools:src="@android:drawable/presence_online" /> + tools:src="@android:drawable/presence_online" + android:contentDescription="@string/user_status" /> + + + + + + + \ No newline at end of file diff --git a/app/src/admin/res/values/strings.xml b/app/src/admin/res/values/strings.xml new file mode 100644 index 0000000..dbfce83 --- /dev/null +++ b/app/src/admin/res/values/strings.xml @@ -0,0 +1,5 @@ + + Driver Admin + + Hello blank fragment + \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/BaseTestRobot.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/BaseTestRobot.kt index 0da5e65..bf99b98 100644 --- a/app/src/androidTest/java/h_mal/appttude/com/driver/BaseTestRobot.kt +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/BaseTestRobot.kt @@ -1,17 +1,44 @@ package h_mal.appttude.com.driver +import android.app.Activity +import android.app.Instrumentation +import android.content.Intent import android.content.res.Resources +import android.net.Uri +import android.view.View +import android.widget.DatePicker +import android.widget.ListView import androidx.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.test.espresso.Espresso.onData import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction import androidx.test.espresso.ViewInteraction import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.swipeDown import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.contrib.PickerActions +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.Intents.intending +import androidx.test.espresso.intent.matcher.IntentMatchers +import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction +import androidx.test.espresso.matcher.ViewMatchers.hasDescendant +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withClassName import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import h_mal.appttude.com.driver.helpers.DataHelper +import h_mal.appttude.com.driver.helpers.EspressoHelper.waitForView import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.anything +import org.hamcrest.Matcher +import org.hamcrest.Matchers +import java.io.File +@SuppressWarnings("unused") open class BaseTestRobot { fun fillEditText(resId: Int, text: String?): ViewInteraction = @@ -21,25 +48,133 @@ open class BaseTestRobot { ) fun clickButton(resId: Int): ViewInteraction = - onView((withId(resId))).perform(ViewActions.click()) + onView((withId(resId))).perform(click()) - fun textView(resId: Int): ViewInteraction = onView(withId(resId)) + fun matchView(resId: Int): ViewInteraction = onView(withId(resId)) + + fun matchViewWaitFor(resId: Int): ViewInteraction = waitForView(withId(resId)) fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction - .check(matches(ViewMatchers.withText(text))) + .check(matches(withText(text))) - fun matchText(resId: Int, text: String): ViewInteraction = matchText(textView(resId), text) + fun matchText(viewId: Int, textId: Int): ViewInteraction = onView(withId(viewId)) + .check(matches(withText(textId))) + + fun matchText(resId: Int, text: String): ViewInteraction = matchText(matchView(resId), text) fun clickListItem(listRes: Int, position: Int) { onData(anything()) .inAdapterView(allOf(withId(listRes))) - .atPosition(position).perform(ViewActions.click()) + .atPosition(position).perform(click()) + } + + fun scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction? { + return matchView(recyclerId) + .perform( + // scrollTo will fail the test if no item matches. + RecyclerViewActions.scrollTo( + hasDescendant(withText(text)) + ) + ) + } + + fun scrollToRecyclerItem(recyclerId: Int, resIdForString: Int): ViewInteraction? { + return matchView(recyclerId) + .perform( + // scrollTo will fail the test if no item matches. + RecyclerViewActions.scrollTo( + hasDescendant(withText(resIdForString)) + ) + ) + } + + fun scrollToRecyclerItemByPosition(recyclerId: Int, position: Int): ViewInteraction? { + return matchView(recyclerId) + .perform( + // scrollTo will fail the test if no item matches. + RecyclerViewActions.scrollToPosition(position) + ) + } + + fun clickViewInRecycler(recyclerId: Int, text: String) { + matchView(recyclerId) + .perform( + // scrollTo will fail the test if no item matches. + RecyclerViewActions.actionOnItem(hasDescendant(withText(text)), click()) + ) + } + + fun clickViewInRecycler(recyclerId: Int, resIdForString: Int) { + matchView(recyclerId) + .perform( + // scrollTo will fail the test if no item matches. + RecyclerViewActions.actionOnItem(hasDescendant(withText(resIdForString)), click()) + ) + } + + fun clickSubViewInRecycler(recyclerId: Int, text: String, subView: Int) { + scrollToRecyclerItem(recyclerId, text) + ?.perform( + // scrollTo will fail the test if no item matches. + RecyclerViewActions.actionOnItem( + hasDescendant(withText(text)), object : ViewAction { + override fun getDescription(): String = "Matching recycler descendant" + override fun getConstraints(): Matcher? = isRoot() + override fun perform(uiController: UiController?, view: View?) { + view?.findViewById(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, 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() + } } \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/BaseUiTest.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/BaseUiTest.kt index cc9e4fb..376fdf3 100644 --- a/app/src/androidTest/java/h_mal/appttude/com/driver/BaseUiTest.kt +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/BaseUiTest.kt @@ -1,35 +1,65 @@ package h_mal.appttude.com.driver +import android.Manifest +import android.R +import android.app.Activity +import android.content.Context +import android.os.Build import android.view.View +import android.view.WindowManager import androidx.annotation.StringRes import androidx.test.core.app.ActivityScenario +import androidx.test.espresso.* import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.IdlingRegistry -import androidx.test.espresso.IdlingResource -import androidx.test.espresso.UiController -import androidx.test.espresso.ViewAction -import androidx.test.espresso.matcher.ViewMatchers.isRoot -import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.rule.GrantPermissionRule import h_mal.appttude.com.driver.base.BaseActivity +import h_mal.appttude.com.driver.helpers.BaseViewAction +import h_mal.appttude.com.driver.helpers.SnapshotRule +import org.hamcrest.CoreMatchers +import org.hamcrest.Description import org.hamcrest.Matcher +import org.hamcrest.TypeSafeMatcher +import org.hamcrest.core.AllOf import org.junit.After import org.junit.Before +import org.junit.ClassRule +import org.junit.Rule +import tools.fastlane.screengrab.Screengrab +import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy +import tools.fastlane.screengrab.locale.LocaleTestRule -open class BaseUiTest>( +open class BaseUiTest>( private val activity: Class ) { private lateinit var mActivityScenarioRule: ActivityScenario private var mIdlingResource: IdlingResource? = null + private lateinit var currentActivity: Activity + + @get:Rule + var permissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + + @get:Rule + var snapshotRule: SnapshotRule = SnapshotRule() + + @Rule + @JvmField + var localeTestRule = LocaleTestRule() + @Before fun setup() { + Screengrab.setDefaultScreenshotStrategy(UiAutomatorScreenshotStrategy()) beforeLaunch() mActivityScenarioRule = ActivityScenario.launch(activity) mActivityScenarioRule.onActivity { mIdlingResource = it.getIdlingResource()!! IdlingRegistry.getInstance().register(mIdlingResource) + afterLaunch(it) } } @@ -41,7 +71,7 @@ open class BaseUiTest>( } fun getResourceString(@StringRes stringRes: Int): String { - return InstrumentationRegistry.getInstrumentation().targetContext.resources.getString( + return getInstrumentation().targetContext.resources.getString( stringRes ) } @@ -49,7 +79,7 @@ open class BaseUiTest>( fun waitFor(delay: Long) { onView(isRoot()).perform(object : ViewAction { override fun getConstraints(): Matcher = isRoot() - override fun getDescription(): String? = "wait for $delay milliseconds" + override fun getDescription(): String = "wait for $delay milliseconds" override fun perform(uiController: UiController, v: View?) { uiController.loopMainThreadForAtLeast(delay) } @@ -57,4 +87,55 @@ open class BaseUiTest>( } open fun beforeLaunch() {} + open fun afterLaunch(context: Context) {} + + + @Suppress("DEPRECATION") + fun checkToastMessage(message: String) { + onView(withText(message)).inRoot(object : TypeSafeMatcher() { + override fun describeTo(description: Description?) { + description?.appendText("is toast") + } + + override fun matchesSafely(root: Root): Boolean { + root.run { + if (windowLayoutParams.get().type == WindowManager.LayoutParams.TYPE_TOAST) { + decorView.run { + if (windowToken === applicationWindowToken) { + // windowToken == appToken means this window isn't contained by any other windows. + // if it was a window for an activity, it would have TYPE_BASE_APPLICATION. + return true + } + } + } + } + return false + } + } + ).check(matches(isDisplayed())) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + waitFor(3500) + } + } + + fun checkSnackBarDisplayedByMessage(message: String) { + onView( + CoreMatchers.allOf( + withId(com.google.android.material.R.id.snackbar_text), + withText(message) + ) + ).check(matches(isDisplayed())) + } + + private fun getCurrentActivity(): Activity { + onView(AllOf.allOf(withId(R.id.content), isDisplayed())) + .perform(object : BaseViewAction() { + override fun setPerform(uiController: UiController?, view: View?) { + if (view?.context is Activity) { + currentActivity = view.context as Activity + } + } + }) + return currentActivity + } } \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/Constants.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/Constants.kt index 5336d08..211d073 100644 --- a/app/src/androidTest/java/h_mal/appttude/com/driver/Constants.kt +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/Constants.kt @@ -7,4 +7,8 @@ const val deleteAccountFirebase = "http://10.0.2.2:9099/identitytoolkit.googleapis.com/v1/accounts:delete?key=$apiKey" -const val USER_PASSWORD = "LetMeIn123!" \ No newline at end of file +const val USER_PASSWORD = "LetMeIn123!" + +const val DRIVER_EMAIL = "existing-driver@driver.com" +const val ADMIN_EMAIL = "admin@driver.com" +const val PASSWORD = "test123456" \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/CustomViewMatchers.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/CustomViewMatchers.kt index 148b37a..09472ef 100644 --- a/app/src/androidTest/java/h_mal/appttude/com/driver/CustomViewMatchers.kt +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/CustomViewMatchers.kt @@ -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? { +fun checkErrorMessage(expectedErrorText: String): Matcher { return object : TypeSafeMatcher() { override fun matchesSafely(view: View?): Boolean { if (view is EditText) { @@ -28,3 +30,25 @@ fun checkErrorMessage(expectedErrorText: String): Matcher? { } } +fun checkImage(): Matcher { + return object : TypeSafeMatcher() { + 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 + } + } +} + diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/FirebaseTest.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/FirebaseTest.kt index 00302cc..6c93287 100644 --- a/app/src/androidTest/java/h_mal/appttude/com/driver/FirebaseTest.kt +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/FirebaseTest.kt @@ -5,18 +5,23 @@ import com.google.firebase.database.FirebaseDatabase import com.google.firebase.storage.FirebaseStorage import h_mal.appttude.com.driver.base.BaseActivity import h_mal.appttude.com.driver.data.FirebaseAuthSource +import h_mal.appttude.com.driver.data.FirebaseDatabaseSource +import h_mal.appttude.com.driver.data.FirebaseStorageSource import kotlinx.coroutines.runBlocking import kotlinx.coroutines.tasks.await import org.junit.After import org.junit.BeforeClass -open class FirebaseTest>( +open class FirebaseTest>( activity: Class, private val registered: Boolean = false, - private val signedIn: Boolean = false + private val signedIn: Boolean = false, + private val signOutAfterTest: Boolean = true ) : BaseUiTest(activity) { private val firebaseAuthSource by lazy { FirebaseAuthSource() } + private val firebaseDatabaseSource by lazy { FirebaseDatabaseSource() } + private val firebaseStorageSource by lazy { FirebaseStorageSource() } private var email: String? = null @@ -45,9 +50,10 @@ open class FirebaseTest>( } @After - fun tearDownFirebase() = runBlocking { - removeUser() - firebaseAuthSource.logOut() + fun tearDownFirebase() { + if (signOutAfterTest) { + firebaseAuthSource.logOut() + } } suspend fun setupUser( @@ -58,6 +64,14 @@ open class FirebaseTest>( firebaseAuthSource.registerUser(signInEmail, password).await().user } + suspend fun login( + signInEmail: String, + password: String + ) { + email = signInEmail + firebaseAuthSource.signIn(signInEmail, password).await() + } + // remove the user we created for testing suspend fun removeUser() { try { @@ -82,9 +96,6 @@ open class FirebaseTest>( } fun getEmail(): String? { - firebaseAuthSource.getUser()?.email?.let { - return it - } - return email + return firebaseAuthSource.getUser()?.email ?: email } } \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/FormRobot.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/FormRobot.kt new file mode 100644 index 0000000..24f2967 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/FormRobot.kt @@ -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) { + 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) + } + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/WebUtils.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/WebUtils.kt index 1d82427..4b6b6c8 100644 --- a/app/src/androidTest/java/h_mal/appttude/com/driver/WebUtils.kt +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/WebUtils.kt @@ -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 diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/api/FirebaseApi.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/api/FirebaseApi.kt new file mode 100644 index 0000000..7c9a9f0 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/api/FirebaseApi.kt @@ -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 + + @PUT("v1/accounts:signInWithPassword") + suspend fun signInWithPassword(@Body request: SignUpRequest): Response + + @PUT("v1/accounts:sendOobCode") + suspend fun sendOobCode(@Body request: Map): Response + + @PUT("v1/accounts:resetPassword") + suspend fun resetPassword(@Body request: ResetPasswordRequest): Response + + // 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) + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/api/FirebaseApiModule.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/api/FirebaseApiModule.kt new file mode 100644 index 0000000..6eeb6d7 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/api/FirebaseApiModule.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/api/NetworkConnectionInterceptor.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/api/NetworkConnectionInterceptor.kt new file mode 100644 index 0000000..c7084b4 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/api/NetworkConnectionInterceptor.kt @@ -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 + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/OobCodeResponse.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/OobCodeResponse.kt new file mode 100644 index 0000000..a5e83c7 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/OobCodeResponse.kt @@ -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 +) + diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/ResetPasswordRequest.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/ResetPasswordRequest.kt new file mode 100644 index 0000000..02d2893 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/ResetPasswordRequest.kt @@ -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 +) + diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/ResetPasswordResponse.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/ResetPasswordResponse.kt new file mode 100644 index 0000000..9a012f2 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/ResetPasswordResponse.kt @@ -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 +) + diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/SignUpRequest.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/SignUpRequest.kt new file mode 100644 index 0000000..35f3abc --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/SignUpRequest.kt @@ -0,0 +1,7 @@ +package h_mal.appttude.com.driver.firebase.model + +data class SignUpRequest( + val password: String? = null, + val email: String? = null +) + diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/SignUpResponse.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/SignUpResponse.kt similarity index 81% rename from app/src/androidTest/java/h_mal/appttude/com/driver/firebase/SignUpResponse.kt rename to app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/SignUpResponse.kt index 4ae760e..8ca9e4b 100644 --- a/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/SignUpResponse.kt +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/firebase/model/SignUpResponse.kt @@ -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, diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/BaseMatcher.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/BaseMatcher.kt new file mode 100644 index 0000000..b13e895 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/BaseMatcher.kt @@ -0,0 +1,17 @@ +package h_mal.appttude.com.driver.helpers + +import android.view.View +import org.hamcrest.BaseMatcher +import org.hamcrest.Description + +class BaseMatcher: BaseMatcher() { + override fun describeTo(description: Description?) { + TODO("Not yet implemented") + } + + override fun matches(actual: Any?): Boolean { + TODO("Not yet implemented") + } + + +} \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/BaseViewAction.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/BaseViewAction.kt new file mode 100644 index 0000000..28be005 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/BaseViewAction.kt @@ -0,0 +1,27 @@ +package h_mal.appttude.com.driver.helpers + +import android.view.View +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom +import org.hamcrest.Matcher + +open class BaseViewAction: ViewAction { + override fun getDescription(): String? = setDescription() + + override fun getConstraints(): Matcher = setConstraints() + + override fun perform(uiController: UiController?, view: View?) { + setPerform(uiController, view) + } + + open fun setDescription(): String? { + return null + } + + open fun setConstraints(): Matcher { + return isAssignableFrom(View::class.java) + } + + open fun setPerform(uiController: UiController?, view: View?) { } +} \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/Constants.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/Constants.kt new file mode 100644 index 0000000..31ba4f7 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/Constants.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/DataHelper.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/DataHelper.kt new file mode 100644 index 0000000..bc2e262 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/DataHelper.kt @@ -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): 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) { + filePaths.forEach { addItem(createClipItem(it)) } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/EspressoHelper.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/EspressoHelper.kt new file mode 100644 index 0000000..08ade1e --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/EspressoHelper.kt @@ -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): ViewAction { + + return object : ViewAction { + + override fun getConstraints(): Matcher = 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 = 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 { + return object : BaseMatcher() { + 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, + 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") + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/SnapshotRule.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/SnapshotRule.kt new file mode 100644 index 0000000..90a9911 --- /dev/null +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/helpers/SnapshotRule.kt @@ -0,0 +1,20 @@ +package h_mal.appttude.com.driver.helpers + +import org.junit.rules.TestWatcher +import org.junit.runner.Description +import tools.fastlane.screengrab.Screengrab +import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy + +/** + * Junit rule that takes a screenshot when a test fails. + */ +class SnapshotRule : TestWatcher() { + override fun failed(e: Throwable, description: Description) { + // Catch a screenshot on failure + Screengrab.screenshot("FAILURE-" + getScreenshotName(description)) + } + + fun getScreenshotName(description: Description): String { + return description.className.replace(".", "-") + "_" + description.methodName.replace(".", "-") + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/LoginRobot.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/robots/LoginRobot.kt similarity index 74% rename from app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/LoginRobot.kt rename to app/src/androidTest/java/h_mal/appttude/com/driver/robots/LoginRobot.kt index c7a76f9..39d005e 100644 --- a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/LoginRobot.kt +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/robots/LoginRobot.kt @@ -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() + } + } \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/RegisterRobot.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/robots/RegisterRobot.kt similarity index 84% rename from app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/RegisterRobot.kt rename to app/src/androidTest/java/h_mal/appttude/com/driver/robots/RegisterRobot.kt index d0bced1..b334357 100644 --- a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/RegisterRobot.kt +++ b/app/src/androidTest/java/h_mal/appttude/com/driver/robots/RegisterRobot.kt @@ -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) diff --git a/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/robots/ApproverRobot.kt b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/robots/ApproverRobot.kt new file mode 100644 index 0000000..22a74a0 --- /dev/null +++ b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/robots/ApproverRobot.kt @@ -0,0 +1,13 @@ +package h_mal.appttude.com.driver.robots + +import h_mal.appttude.com.driver.BaseTestRobot +import h_mal.appttude.com.driver.R + + +fun approver(func: ApproverRobot.() -> Unit) = ApproverRobot().apply { func() } +class ApproverRobot : BaseTestRobot() { + + fun clickApprove() = clickButton(R.id.approve) + fun clickDecline() = clickButton(R.id.decline) + +} \ No newline at end of file diff --git a/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/robots/DriverOverviewRobot.kt b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/robots/DriverOverviewRobot.kt new file mode 100644 index 0000000..b0b2470 --- /dev/null +++ b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/robots/DriverOverviewRobot.kt @@ -0,0 +1,36 @@ +package h_mal.appttude.com.driver.robots + +import android.view.View +import androidx.test.espresso.Espresso.onData +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import h_mal.appttude.com.driver.BaseTestRobot +import h_mal.appttude.com.driver.R +import h_mal.appttude.com.driver.helpers.EspressoHelper.waitForView +import org.hamcrest.BaseMatcher +import org.hamcrest.CoreMatchers.anything + + +fun driverOverview(func: DriverOverviewRobot.() -> Unit) = DriverOverviewRobot().apply { func() } +class DriverOverviewRobot : BaseTestRobot() { + + fun clickOnItemAtPosition(index: Int) = + onData(anything()) + .inAdapterView(withId(R.id.approvals_list)) + .atPosition(index) + .perform(click()) + + fun matchView(position: Int, status: String) = + onData(anything()) + .inAdapterView(withId(R.id.approvals_list)) + .atPosition(position) + .onChildView(withText(status)) + .check(matches(isDisplayed())) + + fun waitForListViewToDisplay() { + waitForView(withId(R.id.approval_status)) + } +} \ No newline at end of file diff --git a/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/robots/HomeAdminRobot.kt b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/robots/HomeAdminRobot.kt new file mode 100644 index 0000000..2cd8bfb --- /dev/null +++ b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/robots/HomeAdminRobot.kt @@ -0,0 +1,53 @@ +package h_mal.appttude.com.driver.robots + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.contrib.DrawerActions +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withTagKey +import h_mal.appttude.com.driver.BaseTestRobot +import h_mal.appttude.com.driver.R +import h_mal.appttude.com.driver.base.CustomViewHolder +import h_mal.appttude.com.driver.model.DatabaseStatus + +fun homeAdmin(func: HomeAdminRobot.() -> Unit) = HomeAdminRobot().apply { func() } +class HomeAdminRobot : BaseTestRobot() { + + fun waitUntilDisplayed() { + matchViewWaitFor(R.id.recycler_view) + } + + fun openDrawer() { + onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()) + } + + fun closeDrawer() { + onView(withId(R.id.drawer_layout)).perform(DrawerActions.close()) + } + + fun updateProfile() { + openDrawer() + clickButton(R.id.nav_user_settings) + } + + fun clickOnItem(anyText: String) = + clickViewInRecycler>(R.id.recycler_view, anyText) + + fun clickOnDriverIdentifier(anyText: String) = + clickSubViewInRecycler>(R.id.recycler_view, anyText, R.id.driver_no) + + fun submitDialog(text: String) { + onView(withTagKey(R.string.driver_identifier)).perform( + ViewActions.replaceText(text), + ViewActions.closeSoftKeyboard() + ) + // Click OK + onView(withId(android.R.id.button1)).perform(ViewActions.click()) + } + + fun showNoPermissionsDisplay() { + matchViewWaitFor(R.id.header) + matchText(R.id.header, DatabaseStatus.NO_PERMISSION.header) + matchText(R.id.subtext, DatabaseStatus.NO_PERMISSION.subtext) + } +} \ No newline at end of file diff --git a/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/tests/AdminBaseTest.kt b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/tests/AdminBaseTest.kt new file mode 100644 index 0000000..60a6772 --- /dev/null +++ b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/tests/AdminBaseTest.kt @@ -0,0 +1,15 @@ +package h_mal.appttude.com.driver.tests + +import h_mal.appttude.com.driver.ADMIN_EMAIL +import h_mal.appttude.com.driver.FirebaseTest +import h_mal.appttude.com.driver.PASSWORD +import h_mal.appttude.com.driver.ui.MainActivity +import kotlinx.coroutines.runBlocking + +open class AdminBaseTest: FirebaseTest(MainActivity::class.java) { + override fun beforeLaunch() { + runBlocking { + login(ADMIN_EMAIL, PASSWORD) + } + } +} \ No newline at end of file diff --git a/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/tests/DocumentApproverTest.kt b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/tests/DocumentApproverTest.kt new file mode 100644 index 0000000..d538e1d --- /dev/null +++ b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/tests/DocumentApproverTest.kt @@ -0,0 +1,89 @@ +package h_mal.appttude.com.driver.tests + +import androidx.test.espresso.Espresso +import h_mal.appttude.com.driver.R +import h_mal.appttude.com.driver.robots.approver +import h_mal.appttude.com.driver.robots.driverOverview +import h_mal.appttude.com.driver.robots.homeAdmin +import org.junit.Test + +class DocumentApproverTest : AdminBaseTest() { + + @Test + fun loginAsAdmin_approveDocumentForDriver_documentApproved() { + homeAdmin { + waitUntilDisplayed() + clickOnItem("kabirmhkhan@gmail.com") + } + // Approve check + driverOverview { + waitForListViewToDisplay() + clickOnItemAtPosition(0) + } + approver { + clickApprove() + checkToastMessage("Document already approved") + Espresso.pressBack() + } + driverOverview { + waitForListViewToDisplay() + clickOnItemAtPosition(2) + } + approver { + clickApprove() + Espresso.pressBack() + } + driverOverview { + waitForListViewToDisplay() + matchView(2, getResourceString(R.string.approved)) + } + } + + @Test + fun loginAsAdmin_declineDocumentForDriver_documentDeclined() { + homeAdmin { + waitUntilDisplayed() + // TODO: find a better way to waitw + waitFor(1200) + clickOnItem("kabirmhkhan@gmail.com") + } + + // Decline check + driverOverview { + waitForListViewToDisplay() + clickOnItemAtPosition(3) + } + approver { + clickDecline() + checkToastMessage("Document already declined") + Espresso.pressBack() + } + driverOverview { + waitForListViewToDisplay() + clickOnItemAtPosition(1) + } + approver { + clickDecline() + Espresso.pressBack() + } + driverOverview { + waitForListViewToDisplay() + matchView(1, getResourceString(R.string.denied)) + } + } + + @Test + fun loginAsAdmin_verifyNoDocumentForNewDriver() { + homeAdmin { + waitUntilDisplayed() + clickOnItem("fanasid@gmail.com") + } + driverOverview { + waitForListViewToDisplay() + matchView(0, getResourceString(R.string.not_submitted)) + clickOnItemAtPosition(0) + matchView(0, getResourceString(R.string.not_submitted)) + } + } + +} \ No newline at end of file diff --git a/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/tests/UserListTest.kt b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/tests/UserListTest.kt new file mode 100644 index 0000000..d932b4b --- /dev/null +++ b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/tests/UserListTest.kt @@ -0,0 +1,36 @@ +package h_mal.appttude.com.driver.tests + +import h_mal.appttude.com.driver.ADMIN_EMAIL +import h_mal.appttude.com.driver.DRIVER_EMAIL +import h_mal.appttude.com.driver.FirebaseTest +import h_mal.appttude.com.driver.robots.homeAdmin +import h_mal.appttude.com.driver.robots.login +import h_mal.appttude.com.driver.ui.user.LoginActivity +import org.junit.Test +import java.io.IOException + +class UserListTest : FirebaseTest(LoginActivity::class.java) { + + @Test + fun loginAsAdmin_updateDriverIdentifier_loggedIn() { + login { + attemptLogin(ADMIN_EMAIL) + } + homeAdmin { + clickOnDriverIdentifier("rsaif660@gmail.com") + submitDialog("ID45") + } + } + + @Test + fun loginAsUser_unableToSeeDrivers_loggedIn() { + // Test fails on CI +// login { +// attemptLogin(DRIVER_EMAIL) +// } +// homeAdmin { +// showNoPermissionsDisplay() +// } + } + +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/DeleteRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/DeleteRobot.kt new file mode 100644 index 0000000..9df821e --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/DeleteRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/DriverScreenRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/DriverScreenRobot.kt new file mode 100644 index 0000000..554f19c --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/DriverScreenRobot.kt @@ -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) + +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/HomeRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/HomeRobot.kt index 50148c3..7e4fe9f 100644 --- a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/HomeRobot.kt +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/HomeRobot.kt @@ -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) } \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdateEmailRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdateEmailRobot.kt new file mode 100644 index 0000000..5170234 --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdateEmailRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdatePasswordRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdatePasswordRobot.kt new file mode 100644 index 0000000..b3b44f0 --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdatePasswordRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdateProfileRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdateProfileRobot.kt new file mode 100644 index 0000000..9e4e15a --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdateProfileRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdateRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdateRobot.kt new file mode 100644 index 0000000..7ddd3fb --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/UpdateRobot.kt @@ -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) + +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/VehicleScreenRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/VehicleScreenRobot.kt new file mode 100644 index 0000000..86ee1ba --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/VehicleScreenRobot.kt @@ -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) + +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/driver/DriversLicenseRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/driver/DriversLicenseRobot.kt new file mode 100644 index 0000000..0c3270f --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/driver/DriversLicenseRobot.kt @@ -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) + + } + +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/driver/DriversProfileRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/driver/DriversProfileRobot.kt new file mode 100644 index 0000000..da44a6a --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/driver/DriversProfileRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/driver/PrivateHireLicenseRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/driver/PrivateHireLicenseRobot.kt new file mode 100644 index 0000000..6f5c9d1 --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/driver/PrivateHireLicenseRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/InsuranceRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/InsuranceRobot.kt new file mode 100644 index 0000000..348e066 --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/InsuranceRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/LogbookRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/LogbookRobot.kt new file mode 100644 index 0000000..33d69ad --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/LogbookRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/MOTRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/MOTRobot.kt new file mode 100644 index 0000000..c6e1456 --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/MOTRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/PrivateHireVehicleLicenseRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/PrivateHireVehicleLicenseRobot.kt new file mode 100644 index 0000000..4dd7a81 --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/PrivateHireVehicleLicenseRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/VehicleProfileRobot.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/VehicleProfileRobot.kt new file mode 100644 index 0000000..a168b4d --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/robots/vehicle/VehicleProfileRobot.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/tests/UserAuthenticationActivityTest.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/tests/UserAuthenticationActivityTest.kt index c2a995a..eb03bde 100644 --- a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/tests/UserAuthenticationActivityTest.kt +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/tests/UserAuthenticationActivityTest.kt @@ -33,7 +33,10 @@ class UserAuthenticationActivityTest : FirebaseTest(LoginActivity } home { checkTitleExists(getResourceString(R.string.welcome_title)) + updateProfile() } + + // TODO: update user details } } diff --git a/app/src/androidTestDriver/java/h_mal/appttude/com/driver/tests/newUser/SubmitNewDataActivityTest.kt b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/tests/newUser/SubmitNewDataActivityTest.kt new file mode 100644 index 0000000..631a08c --- /dev/null +++ b/app/src/androidTestDriver/java/h_mal/appttude/com/driver/tests/newUser/SubmitNewDataActivityTest.kt @@ -0,0 +1,37 @@ +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::class.java, registered = true, signedIn = true) { + + @Test + fun verifyUserRegistration_validUsernameAndPassword_loggedIn() { + home { + waitFor(2500) + checkTitleExists(getResourceString(R.string.welcome_title)) + requestProfile() + openDriverProfile() + } + driverScreen { + driverLicense() + } + driversLicense { + submitForm("SAMPLE8456310LTU", 2022, 10, 2) + } + } + +} diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml index 8451b0c..b30cb5f 100644 --- a/app/src/debug/AndroidManifest.xml +++ b/app/src/debug/AndroidManifest.xml @@ -1,6 +1,14 @@ + + + + + + + + + + + + + + + + + + 10.0.2.2 diff --git a/app/src/driver/AndroidManifest.xml b/app/src/driver/AndroidManifest.xml deleted file mode 100644 index 53dd8d6..0000000 --- a/app/src/driver/AndroidManifest.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - diff --git a/app/src/main/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt b/app/src/driver/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt similarity index 97% rename from app/src/main/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt rename to app/src/driver/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt index 224b355..0f81ab1 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt @@ -5,6 +5,7 @@ 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( @@ -23,6 +24,11 @@ class ApplicationViewModelFactory( auth, storage ) + isAssignableFrom(RoleViewModel::class.java) -> RoleViewModel( + auth, + database, + storage + ) isAssignableFrom(DriverLicenseViewModel::class.java) -> DriverLicenseViewModel( auth, database, @@ -59,11 +65,6 @@ class ApplicationViewModelFactory( database, storage ) - isAssignableFrom(RoleViewModel::class.java) -> RoleViewModel( - auth, - database, - storage - ) else -> throw IllegalArgumentException("Unknown ViewModel class") } as T } diff --git a/app/src/driver/java/h_mal/appttude/com/driver/application/DriverApplication.kt b/app/src/driver/java/h_mal/appttude/com/driver/application/DriverApplication.kt new file mode 100644 index 0000000..855d97a --- /dev/null +++ b/app/src/driver/java/h_mal/appttude/com/driver/application/DriverApplication.kt @@ -0,0 +1,28 @@ +package h_mal.appttude.com.driver.application + +import android.app.Application +import android.content.res.Resources +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 org.kodein.di.Kodein +import org.kodein.di.KodeinAware +import org.kodein.di.android.x.androidXModule +import org.kodein.di.generic.bind +import org.kodein.di.generic.instance +import org.kodein.di.generic.provider +import org.kodein.di.generic.singleton + +class DriverApplication : BaseApplication() { + + override val flavourModule = super.flavourModule.copy { + bind() from provider { + ApplicationViewModelFactory( + instance(), + instance(), + instance() + ) + } + } +} \ No newline at end of file diff --git a/app/src/driver/java/h_mal/appttude/com/driver/ui/MainActivity.kt b/app/src/driver/java/h_mal/appttude/com/driver/ui/MainActivity.kt index 270d7ba..9f2eaf1 100644 --- a/app/src/driver/java/h_mal/appttude/com/driver/ui/MainActivity.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/ui/MainActivity.kt @@ -33,6 +33,7 @@ class MainActivity : DrawerActivity() { setupDrawer(data) } } + } private fun setupDrawer(user: FirebaseUser) { diff --git a/app/src/driver/java/h_mal/appttude/com/driver/ui/driverprofile/DriverProfileFragment.kt b/app/src/driver/java/h_mal/appttude/com/driver/ui/driverprofile/DriverProfileFragment.kt index 91b0022..9bf7614 100644 --- a/app/src/driver/java/h_mal/appttude/com/driver/ui/driverprofile/DriverProfileFragment.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/ui/driverprofile/DriverProfileFragment.kt @@ -37,7 +37,7 @@ class DriverProfileFragment : } } addPhoto.setOnClickListener { openGalleryWithPermissionRequest() } - submitDriver.setOnClickListener { submit() } + submit.setOnClickListener { submit() } } override fun submit() { diff --git a/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/InsuranceFragment.kt b/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/InsuranceFragment.kt index 2f304b1..535fd10 100644 --- a/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/InsuranceFragment.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/InsuranceFragment.kt @@ -34,7 +34,7 @@ class InsuranceFragment : } } uploadInsurance.setOnClickListener { openGalleryWithPermissionRequest() } - submitIns.setOnClickListener { submit() } + submit.setOnClickListener { submit() } } } diff --git a/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/LogbookFragment.kt b/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/LogbookFragment.kt index fc37aa9..be5afe7 100644 --- a/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/LogbookFragment.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/LogbookFragment.kt @@ -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() { diff --git a/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/MotFragment.kt b/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/MotFragment.kt index 8aed6be..f129729 100644 --- a/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/MotFragment.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/ui/vehicleprofile/MotFragment.kt @@ -24,7 +24,7 @@ class MotFragment : DataSubmissionBaseFragment model.isSeized = res } - submitVehicle.setOnClickListener { + submit.setOnClickListener { validateEditTexts( reg, make, diff --git a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/DriverLicenseViewModel.kt b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/DriverLicenseViewModel.kt similarity index 93% rename from app/src/main/java/h_mal/appttude/com/driver/viewmodels/DriverLicenseViewModel.kt rename to app/src/driver/java/h_mal/appttude/com/driver/viewmodels/DriverLicenseViewModel.kt index 04dadfd..3df8d66 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/DriverLicenseViewModel.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/DriverLicenseViewModel.kt @@ -20,7 +20,7 @@ class DriverLicenseViewModel( override val storageRef: StorageReference = storage.driversLicenseStorageRef(uid) override val objectName: String = "drivers license" - override fun getDataFromDatabase() = getDataClass() + override fun getDataFromDatabase() = retrieveDataFromDatabase() override fun setDataInDatabase(data: DriversLicense, localImageUri: Uri?) = io { doTryOperation("Failed to upload $objectName") { diff --git a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/DriverProfileViewModel.kt b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/DriverProfileViewModel.kt similarity index 93% rename from app/src/main/java/h_mal/appttude/com/driver/viewmodels/DriverProfileViewModel.kt rename to app/src/driver/java/h_mal/appttude/com/driver/viewmodels/DriverProfileViewModel.kt index ee6514c..e523c31 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/DriverProfileViewModel.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/DriverProfileViewModel.kt @@ -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() + override fun getDataFromDatabase() = retrieveDataFromDatabase() override fun setDataInDatabase(data: DriverProfile, localImageUri: Uri?) = io { doTryOperation("Failed to upload $objectName") { diff --git a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/InsuranceViewModel.kt b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/InsuranceViewModel.kt similarity index 95% rename from app/src/main/java/h_mal/appttude/com/driver/viewmodels/InsuranceViewModel.kt rename to app/src/driver/java/h_mal/appttude/com/driver/viewmodels/InsuranceViewModel.kt index dff3c6c..328aeaf 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/InsuranceViewModel.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/InsuranceViewModel.kt @@ -20,7 +20,7 @@ class InsuranceViewModel( override val storageRef: StorageReference = storage.insuranceStorageRef(uid) override val objectName: String = "insurance" - override fun getDataFromDatabase() = getDataClass() + override fun getDataFromDatabase() = retrieveDataFromDatabase() override fun setDataInDatabase(data: Insurance, localImageUris: List?) = io { doTryOperation("Failed to upload $objectName") { diff --git a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/LogbookViewModel.kt b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/LogbookViewModel.kt similarity index 94% rename from app/src/main/java/h_mal/appttude/com/driver/viewmodels/LogbookViewModel.kt rename to app/src/driver/java/h_mal/appttude/com/driver/viewmodels/LogbookViewModel.kt index 5f2dd43..0efcb1d 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/LogbookViewModel.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/LogbookViewModel.kt @@ -20,7 +20,7 @@ class LogbookViewModel( override val storageRef: StorageReference = storage.logBookStorageRef(uid) override val objectName: String = "Log book" - override fun getDataFromDatabase() = getDataClass() + override fun getDataFromDatabase() = retrieveDataFromDatabase() override fun setDataInDatabase(data: Logbook, localImageUri: Uri?) = io { doTryOperation("Failed to upload $objectName") { diff --git a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/MotViewModel.kt b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/MotViewModel.kt similarity index 94% rename from app/src/main/java/h_mal/appttude/com/driver/viewmodels/MotViewModel.kt rename to app/src/driver/java/h_mal/appttude/com/driver/viewmodels/MotViewModel.kt index ac932af..3f06454 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/MotViewModel.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/MotViewModel.kt @@ -20,7 +20,7 @@ class MotViewModel( override val storageRef: StorageReference? = storage.motStorageRef(uid) override val objectName: String = "vehicle profile" - override fun getDataFromDatabase() = getDataClass() + override fun getDataFromDatabase() = retrieveDataFromDatabase() override fun setDataInDatabase(data: Mot, localImageUri: Uri?) = io { doTryOperation("Failed to upload $objectName") { diff --git a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/PrivateHireLicenseViewModel.kt b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/PrivateHireLicenseViewModel.kt similarity index 94% rename from app/src/main/java/h_mal/appttude/com/driver/viewmodels/PrivateHireLicenseViewModel.kt rename to app/src/driver/java/h_mal/appttude/com/driver/viewmodels/PrivateHireLicenseViewModel.kt index 0f502b3..e234012 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/PrivateHireLicenseViewModel.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/PrivateHireLicenseViewModel.kt @@ -20,7 +20,7 @@ class PrivateHireLicenseViewModel( override val storageRef: StorageReference = storage.privateHireStorageRef(uid) override val objectName: String = "private hire license" - override fun getDataFromDatabase() = getDataClass() + override fun getDataFromDatabase() = retrieveDataFromDatabase() override fun setDataInDatabase(data: PrivateHireLicense, localImageUri: Uri?) = io { doTryOperation("Failed to upload private hire license") { diff --git a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/PrivateHireVehicleViewModel.kt b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/PrivateHireVehicleViewModel.kt similarity index 93% rename from app/src/main/java/h_mal/appttude/com/driver/viewmodels/PrivateHireVehicleViewModel.kt rename to app/src/driver/java/h_mal/appttude/com/driver/viewmodels/PrivateHireVehicleViewModel.kt index e4bf9be..901d83a 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/PrivateHireVehicleViewModel.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/PrivateHireVehicleViewModel.kt @@ -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() + override fun getDataFromDatabase() = retrieveDataFromDatabase() override fun setDataInDatabase(data: PrivateHireVehicle, localImageUri: Uri?) = io { doTryOperation("Failed to upload $objectName") { diff --git a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/VehicleProfileViewModel.kt b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/VehicleProfileViewModel.kt similarity index 92% rename from app/src/main/java/h_mal/appttude/com/driver/viewmodels/VehicleProfileViewModel.kt rename to app/src/driver/java/h_mal/appttude/com/driver/viewmodels/VehicleProfileViewModel.kt index 0be90ed8..143a935 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/VehicleProfileViewModel.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/viewmodels/VehicleProfileViewModel.kt @@ -19,7 +19,7 @@ class VehicleProfileViewModel( override val storageRef: StorageReference? = null override val objectName: String = "vehicle profile" - override fun getDataFromDatabase() = getDataClass() + override fun getDataFromDatabase() = retrieveDataFromDatabase() override fun setDataInDatabase(data: VehicleProfile) { io { diff --git a/app/src/driver/res/layout/activity_main.xml b/app/src/driver/res/layout/activity_main.xml new file mode 100644 index 0000000..498e7f4 --- /dev/null +++ b/app/src/driver/res/layout/activity_main.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + diff --git a/app/src/driver/res/layout/driver_profile_request.xml b/app/src/driver/res/layout/driver_profile_request.xml new file mode 100644 index 0000000..4eb86e8 --- /dev/null +++ b/app/src/driver/res/layout/driver_profile_request.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_driver_overall.xml b/app/src/driver/res/layout/fragment_driver_overall.xml similarity index 100% rename from app/src/main/res/layout/fragment_driver_overall.xml rename to app/src/driver/res/layout/fragment_driver_overall.xml diff --git a/app/src/main/res/layout/fragment_home_driver.xml b/app/src/driver/res/layout/fragment_home_driver.xml similarity index 100% rename from app/src/main/res/layout/fragment_home_driver.xml rename to app/src/driver/res/layout/fragment_home_driver.xml diff --git a/app/src/main/res/layout/fragment_vehicle_overall.xml b/app/src/driver/res/layout/fragment_vehicle_overall.xml similarity index 100% rename from app/src/main/res/layout/fragment_vehicle_overall.xml rename to app/src/driver/res/layout/fragment_vehicle_overall.xml diff --git a/app/src/driver/res/values/strings.xml b/app/src/driver/res/values/strings.xml index 6048840..22e70ea 100644 --- a/app/src/driver/res/values/strings.xml +++ b/app/src/driver/res/values/strings.xml @@ -1,4 +1,3 @@ - - Hello blank fragment + Driver Choice cars \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2c36fa8..9c321a0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ @@ -25,17 +24,16 @@ - private var dateArchivedText: TextView? = null - override fun getCount(): Int { - return size - } - - override fun getItem(position: Int): Any? { - when (archiveString) { -// FirebaseClass.PRIVATE_HIRE_FIREBASE -> return archiveObject?.private_hire -// ?.get(mKeys[position]) -// FirebaseClass.DRIVERS_LICENSE_FIREBASE -> return archiveObject?.driver_license -// ?.get(mKeys[position]) -// FirebaseClass.VEHICLE_DETAILS_FIREBASE -> return archiveObject?.vehicle_details -// ?.get(mKeys[position]) -// FirebaseClass.MOT_FIREBASE -> return archiveObject?.mot_details?.get(mKeys[position]) -// FirebaseClass.INSURANCE_FIREBASE -> return archiveObject?.insurance_details?.get( -// mKeys[position] -// ) -// FirebaseClass.LOG_BOOK_FIREBASE -> return archiveObject?.log_book -// ?.get(mKeys.get(position)) -// FirebaseClass.PRIVATE_HIRE_VEHICLE_LICENSE -> return archiveObject?.ph_car?.get(mKeys[position]) - else -> return mKeys[position] - } - } - - override fun getItemId(position: Int): Long { - return position.toLong() - } - - override fun getView(position: Int, convertView: View, parent: ViewGroup): View { - var listItemView: View = convertView -// -// if (listItemView == null) { -// if (((archiveString == FirebaseClass.PRIVATE_HIRE_FIREBASE) || (archiveString == FirebaseClass.DRIVERS_LICENSE_FIREBASE) || (archiveString == FirebaseClass.MOT_FIREBASE) || (archiveString == FirebaseClass.LOG_BOOK_FIREBASE) || (archiveString == FirebaseClass.PRIVATE_HIRE_VEHICLE_LICENSE))) { -// listItemView = LayoutInflater.from(context).inflate( -// R.layout.archive_license_item, parent, false -// ) -// val imageView: ImageView = listItemView.findViewById(R.id.image_archive) -// dateArchivedText = listItemView.findViewById(R.id.date_archived) -// val expiryHolder: LinearLayout = listItemView.findViewById(R.id.expiry_view) -// val fieldTwo: LinearLayout = listItemView.findViewById(R.id.field_two_view) -// val expiryText: TextView = listItemView.findViewById(R.id.exp_text) -// val fiewTwoLable: TextView = listItemView.findViewById(R.id.field_two) -// val fieldTwoText: TextView = listItemView.findViewById(R.id.field_two_text) -// when (archiveString) { -//// FirebaseClass.PRIVATE_HIRE_FIREBASE -> { -//// expiryHolder.visibility = View.VISIBLE -//// fieldTwo.visibility = View.VISIBLE -//// val privateHireObject: PrivateHireObject = -//// getItem(position) as PrivateHireObject -//// Picasso.get().load(privateHireObject.phImageString) -//// .placeholder(R.drawable.choice_img) -//// .into(imageView) -//// dateString(position) -//// expiryText.text = privateHireObject.phExpiry -//// fiewTwoLable.text = "Private Hire License No.:" -//// fieldTwoText.text = privateHireObject.phNumber -//// } -//// FirebaseClass.DRIVERS_LICENSE_FIREBASE -> { -//// expiryHolder.visibility = View.VISIBLE -//// fieldTwo.visibility = View.VISIBLE -//// val driversLicenseObject: DriversLicenseObject = -//// getItem(position) as DriversLicenseObject -//// Picasso.get().load(driversLicenseObject.licenseImageString) -//// .placeholder(R.drawable.choice_img) -//// .into(imageView) -//// dateString(position) -//// expiryText.text = driversLicenseObject.licenseExpiry -//// fiewTwoLable.text = "License No.:" -//// fieldTwoText.text = driversLicenseObject.licenseNumber -//// } -//// FirebaseClass.MOT_FIREBASE -> { -//// expiryHolder.visibility = View.VISIBLE -//// fieldTwo.visibility = View.GONE -//// val motObject: MotObject = getItem(position) as MotObject -//// Picasso.get().load(motObject.motImageString) -//// .placeholder(R.drawable.choice_img) -//// .into(imageView) -//// dateString(position) -//// expiryText.text = motObject.motExpiry -//// } -//// FirebaseClass.LOG_BOOK_FIREBASE -> { -//// expiryHolder.visibility = View.GONE -//// fieldTwo.visibility = View.VISIBLE -//// val logbookObject: LogbookObject = getItem(position) as LogbookObject -//// Picasso.get().load(logbookObject.photoString) -//// .into(MainActivity.loadImage(imageView)) -//// dateString(position) -//// fiewTwoLable.text = "V5C No.:" -//// fieldTwoText.text = logbookObject.v5cnumber -//// } -//// FirebaseClass.PRIVATE_HIRE_VEHICLE_LICENSE -> { -//// expiryHolder.visibility = View.VISIBLE -//// fieldTwo.visibility = View.VISIBLE -//// val privateHireVehicleObject: PrivateHireVehicleObject = -//// getItem(position) as PrivateHireVehicleObject -//// Picasso.get().load(privateHireVehicleObject.phCarImageString) -//// .into(MainActivity.loadImage(imageView)) -//// dateString(position) -//// expiryText.text = privateHireVehicleObject.phCarExpiry -//// fiewTwoLable.text = "Private Hire Vehicle License No.:" -//// fieldTwoText.text = privateHireVehicleObject.phCarNumber -//// } -//// } -//// } else if ((archiveString == FirebaseClass.INSURANCE_FIREBASE)) { -//// listItemView = LayoutInflater.from(context).inflate( -//// R.layout.archive_insurance_item, parent, false -//// ) -//// val holder: View = listItemView.findViewById(R.id.image_pager) -//// val swiperClass: ImageSwiperClass = ImageSwiperClass(context, holder) -//// // swiperClass.hideDelete(); -//// listItemView.findViewById(R.id.delete).visibility = View.GONE -//// // holder.findViewById(R.id.delete).setVisibility(View.INVISIBLE); -//// dateArchivedText = listItemView.findViewById(R.id.date_archived) -//// dateString(position) -//// val expiryText: TextView = listItemView.findViewById(R.id.exp_text) -//// val fieldTwoText: TextView = listItemView.findViewById(R.id.archive_insurer) -//// val insuranceObject: InsuranceObject = getItem(position) as InsuranceObject -////// swiperClass.reinstantiateList(insuranceObject.photoStrings) -//// expiryText.text = insuranceObject.expiryDate -//// fieldTwoText.text = insuranceObject.insurerName -//// } else if ((archiveString == FirebaseClass.VEHICLE_DETAILS_FIREBASE)) { -//// listItemView = LayoutInflater.from(context).inflate( -//// R.layout.archive_vehicle_item, parent, false -//// ) -//// dateArchivedText = listItemView.findViewById(R.id.date_archived) -//// dateString(position) -//// val numberPlate: TextView = listItemView.findViewById(R.id.number_plate) -//// val keeperName: TextView = listItemView.findViewById(R.id.keeper_name) -//// val keeperAddress: TextView = listItemView.findViewById(R.id.keeper_address) -//// val carText: TextView = listItemView.findViewById(R.id.car_text_arch) -//// val carColour: TextView = listItemView.findViewById(R.id.car_colour) -//// val carSeized: TextView = listItemView.findViewById(R.id.seized_checkbox) -//// val startDate: TextView = listItemView.findViewById(R.id.first_date) -//// val vehicleProfileObject: VehicleProfileObject = -//// getItem(position) as VehicleProfileObject -//// numberPlate.text = vehicleProfileObject.reg -//// keeperName.text = vehicleProfileObject.keeperName -//// keeperAddress.text = vehicleProfileObject.keeperAddress + "\n" + vehicleProfileObject.keeperPostCode -//// carText.text = vehicleProfileObject.make + " " + vehicleProfileObject.model -//// carColour.text = vehicleProfileObject.colour -//// val s: String -//// if (vehicleProfileObject.isSeized) { -//// s = "Yes" -//// } else { -//// s = "No" -//// } -//// carSeized.text = s -//// startDate.text = vehicleProfileObject.startDate -//// } -//// } - return listItemView - } - - private fun dateString(position: Int) { - var success: Boolean = true - try { - dateArchivedText!!.text = - mKeys[position].convertDateStringDatePattern("yyyyMMdd_HHmmss", "dd/MM/yyyy") - } catch (e: ParseException) { - e.printStackTrace() - success = false - } finally { - if (!success) { - dateArchivedText!!.text = mKeys.get(position).substring(0, 8) - } - } - } - - companion object { - private val TAG: String = "ArchiveObjectListAdapte" - } - -// init { -// archiveObject?.apply { -// val map = when (archiveString) { -// FirebaseClass.PRIVATE_HIRE_FIREBASE -> private_hire -// FirebaseClass.DRIVERS_LICENSE_FIREBASE -> driver_license -// FirebaseClass.VEHICLE_DETAILS_FIREBASE -> vehicle_details -// FirebaseClass.MOT_FIREBASE -> mot_details -// FirebaseClass.INSURANCE_FIREBASE -> insurance_details -// FirebaseClass.LOG_BOOK_FIREBASE -> log_book -// FirebaseClass.PRIVATE_HIRE_VEHICLE_LICENSE -> ph_car -// else -> null -// } -// setUp(map) -// } -// -// } - - private fun setUp(map: HashMap?) { - size = map?.size ?: 0 - map?.keys?.toTypedArray()?.let { - mKeys = it - } - } -} \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/base/BaseActivity.kt b/app/src/main/java/h_mal/appttude/com/driver/base/BaseActivity.kt index 0cef4bd..c20577e 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/base/BaseActivity.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/base/BaseActivity.kt @@ -1,29 +1,36 @@ package h_mal.appttude.com.driver.base import android.content.Intent +import android.os.Build import android.os.Bundle -import android.view.LayoutInflater import android.view.View +import android.view.View.OnAttachStateChangeListener import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.inflate +import android.widget.Toast import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.ViewModelLazy import androidx.test.espresso.IdlingResource import androidx.viewbinding.ViewBinding +import com.google.android.material.snackbar.BaseTransientBottomBar.BaseCallback +import com.google.android.material.snackbar.Snackbar import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.application.ApplicationViewModelFactory import h_mal.appttude.com.driver.data.ViewState -import h_mal.appttude.com.driver.utils.* +import h_mal.appttude.com.driver.utils.BasicIdlingResource +import h_mal.appttude.com.driver.utils.GenericsHelper.getGenericClassAt +import h_mal.appttude.com.driver.utils.GenericsHelper.inflateBindingByType +import h_mal.appttude.com.driver.utils.hide +import h_mal.appttude.com.driver.utils.show +import h_mal.appttude.com.driver.utils.triggerAnimation import org.kodein.di.KodeinAware import org.kodein.di.android.kodein import org.kodein.di.generic.instance -import java.lang.reflect.ParameterizedType -import kotlin.reflect.KClass - -abstract class BaseActivity : AppCompatActivity(), KodeinAware { +abstract class BaseActivity : AppCompatActivity(), + KodeinAware { // The Idling Resource which will be null in production. private var mIdlingResource: BasicIdlingResource? = null private lateinit var loadingView: View @@ -47,34 +54,10 @@ abstract class BaseActivity : AppCompatActi { defaultViewModelCreationExtras } ) - @Suppress("UNCHECKED_CAST") - fun Any.getGenericClassAt(position: Int): KClass = - ((javaClass.genericSuperclass as? ParameterizedType) - ?.actualTypeArguments?.getOrNull(position) as? Class) - ?.kotlin - ?: throw IllegalStateException("Can not find class from generic argument") - - /** - * Create a view binding out of the the generic [VB] - */ - private fun inflateBindingByType( - genericClassAt: KClass - ): VB = try { - @Suppress("UNCHECKED_CAST") - genericClassAt.java.methods.first { viewBinding -> - viewBinding.parameterTypes.size == 1 - && viewBinding.parameterTypes.getOrNull(0) == LayoutInflater::class.java - }.invoke(null, layoutInflater) as VB - } catch (exception: Exception) { - throw IllegalStateException("Can not inflate binding from generic") - } - - private var loading: Boolean = false - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) configureObserver() - _binding = inflateBindingByType(getGenericClassAt(1)) + _binding = inflateBindingByType(getGenericClassAt(1), layoutInflater) setContentView(requireNotNull(_binding).root) setupView(binding) } @@ -119,7 +102,6 @@ abstract class BaseActivity : AppCompatActi */ open fun onStarted() { loadingView.fadeIn() - loading = true mIdlingResource?.setIdleState(false) } @@ -128,7 +110,6 @@ abstract class BaseActivity : AppCompatActi */ open fun onSuccess(data: Any?) { loadingView.fadeOut() - loading = false mIdlingResource?.setIdleState(true) } @@ -136,9 +117,8 @@ abstract class BaseActivity : AppCompatActi * Called in case of failure or some error emitted from the liveData in viewModel */ open fun onFailure(error: String?) { - error?.let { displayToast(it) } + error?.let { showToast(it) } loadingView.fadeOut() - loading = false mIdlingResource?.setIdleState(true) } @@ -164,7 +144,46 @@ abstract class BaseActivity : AppCompatActi override fun onBackPressed() { - if (!loading) super.onBackPressed() + loadingView.hide() + super.onBackPressed() + } + + fun showToast(message: String) { + val toast = Toast.makeText(this, message, Toast.LENGTH_LONG) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + toast.addCallback(object : Toast.Callback() { + override fun onToastHidden() { + super.onToastHidden() + mIdlingResource?.setIdleState(true) + } + override fun onToastShown() { + super.onToastShown() + mIdlingResource?.setIdleState(false) + } + }) + } else { + + } + toast.show() + } + + fun showSnackBar(message: String) { + val snackbar = Snackbar.make( + window.decorView.findViewById(android.R.id.content), + message, + Snackbar.LENGTH_LONG + ) + snackbar.addCallback(object : BaseCallback() { + override fun onShown(transientBottomBar: Snackbar?) { + super.onShown(transientBottomBar) + mIdlingResource?.setIdleState(false) + } + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + super.onDismissed(transientBottomBar, event) + mIdlingResource?.setIdleState(true) + } + }) + snackbar.show() } /** diff --git a/app/src/main/java/h_mal/appttude/com/driver/base/BaseFirebaseAdapter.kt b/app/src/main/java/h_mal/appttude/com/driver/base/BaseFirebaseAdapter.kt new file mode 100644 index 0000000..62b13c5 --- /dev/null +++ b/app/src/main/java/h_mal/appttude/com/driver/base/BaseFirebaseAdapter.kt @@ -0,0 +1,80 @@ +package h_mal.appttude.com.driver.base + +import android.net.ConnectivityManager +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import androidx.viewbinding.ViewBinding +import com.firebase.ui.database.FirebaseRecyclerAdapter +import com.firebase.ui.database.FirebaseRecyclerOptions +import com.google.firebase.database.DatabaseError +import h_mal.appttude.com.driver.utils.GenericsHelper.getGenericClassAt +import h_mal.appttude.com.driver.utils.GenericsHelper.inflateBindingByType +import java.nio.ByteBuffer + + +open class BaseFirebaseAdapter( + options: FirebaseRecyclerOptions, + private val layoutInflater: LayoutInflater +) : + FirebaseRecyclerAdapter>(options) { + + private val connectivityManager = + layoutInflater.context.getSystemService(ConnectivityManager::class.java) as ConnectivityManager + + private var _binding: VB? = null + val binding: VB + get() = _binding ?: error("Must only access binding while fragment is attached.") + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder { + _binding = inflateBindingByType(getGenericClassAt(1), layoutInflater) + val lp = RecyclerView.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + binding.root.layoutParams = lp + return CustomViewHolder(requireNotNull(_binding)) + } + + override fun onBindViewHolder(holder: CustomViewHolder, position: Int, model: T) {} + + override fun getItemId(position: Int): Long { + return snapshots.getSnapshot(position).key?.toByteArray() + ?.let { ByteBuffer.wrap(it).long } ?: super.getItemId(position) + } + + fun getKeyAtPosition(position: Int) = snapshots.getSnapshot(position).key + + override fun startListening() { + super.startListening() + // check if network is connected + if (connectivityManager.activeNetwork == null) { + connectionLost() + } + } + + open fun connectionLost() {} + override fun onDataChanged() { + super.onDataChanged() + if (itemCount == 0) emptyList() + } + override fun onError(error: DatabaseError) { + super.onError(error) + when (error.code) { + DatabaseError.PERMISSION_DENIED -> permissionsDenied() + DatabaseError.DISCONNECTED, DatabaseError.UNAVAILABLE, DatabaseError.NETWORK_ERROR -> noConnection() + DatabaseError.EXPIRED_TOKEN, DatabaseError.OPERATION_FAILED, DatabaseError.INVALID_TOKEN, DatabaseError.MAX_RETRIES -> authorizationError() + else -> cannotRetrieve() + } + + } + + open fun permissionsDenied() {} + open fun noConnection() {} + open fun cannotRetrieve() {} + open fun authorizationError() {} + open fun emptyList() {} +} + +class CustomViewHolder(val viewBinding: VB) : ViewHolder(viewBinding.root) \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/base/BaseFragment.kt b/app/src/main/java/h_mal/appttude/com/driver/base/BaseFragment.kt index 25e769b..e80b26e 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/base/BaseFragment.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/base/BaseFragment.kt @@ -1,29 +1,27 @@ package h_mal.appttude.com.driver.base -import android.app.Activity import android.content.ClipData +import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContract import androidx.fragment.app.Fragment import androidx.fragment.app.createViewModelLazy import androidx.viewbinding.ViewBinding import h_mal.appttude.com.driver.application.ApplicationViewModelFactory import h_mal.appttude.com.driver.data.ViewState +import h_mal.appttude.com.driver.utils.GenericsHelper.getGenericClassAt +import h_mal.appttude.com.driver.utils.GenericsHelper.inflateBindingByType import h_mal.appttude.com.driver.utils.PermissionsUtils import org.kodein.di.KodeinAware import org.kodein.di.android.x.kodein import org.kodein.di.generic.instance -import java.lang.reflect.ParameterizedType -import kotlin.reflect.KClass -const val IMAGE_SELECT_REQUEST_CODE = 401 - -abstract class BaseFragment( -) : Fragment(), KodeinAware { +abstract class BaseFragment : Fragment(), KodeinAware { private var _binding: VB? = null private val binding: VB @@ -31,7 +29,12 @@ abstract class BaseFragment( var mActivity: BaseActivity? = null + override val kodein by kodein() + private val factory by instance() + val viewModel: V by getFragmentViewModel() + private fun getFragmentViewModel(): Lazy = + createViewModelLazy(getGenericClassAt(0), { viewModelStore }, factoryProducer = { factory }) private var multipleImage: Boolean = false @@ -39,34 +42,6 @@ abstract class BaseFragment( multipleImage = true } - override val kodein by kodein() - val factory by instance() - - fun getFragmentViewModel(): Lazy = - createViewModelLazy(getGenericClassAt(0), { viewModelStore }, factoryProducer = { factory }) - - fun LayoutInflater.inflateBindingByType( - container: ViewGroup?, - genericClassAt: KClass - ): VB = try { - @Suppress("UNCHECKED_CAST") - genericClassAt.java.methods.first { inflateFun -> - inflateFun.parameterTypes.size == 3 - && inflateFun.parameterTypes.getOrNull(0) == LayoutInflater::class.java - && inflateFun.parameterTypes.getOrNull(1) == ViewGroup::class.java - && inflateFun.parameterTypes.getOrNull(2) == Boolean::class.java - }.invoke(null, this, container, false) as VB - } catch (exception: Exception) { - throw IllegalStateException("Can not inflate binding from generic") - } - - @Suppress("UNCHECKED_CAST") - fun Any.getGenericClassAt(position: Int): KClass = - ((javaClass.genericSuperclass as? ParameterizedType) - ?.actualTypeArguments?.getOrNull(position) as? Class) - ?.kotlin - ?: throw IllegalStateException("Can not find class from generic argument") - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -126,25 +101,6 @@ abstract class BaseFragment( } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (resultCode == Activity.RESULT_OK) { - when (requestCode) { - IMAGE_SELECT_REQUEST_CODE -> { - data?.clipData?.convertToList()?.let { clip -> - val list = clip.takeIf { it.size > 10 }?.let { - clip.subList(0, 9) - } ?: clip - onImageGalleryResult(list) - return - } - onImageGalleryResult(data?.data) - } - } - - } - } - private fun ClipData.convertToList(): List = 0.rangeTo(itemCount).map { getItemAt(it).uri } /** @@ -180,10 +136,38 @@ abstract class BaseFragment( open fun onImageGalleryResult(imageUris: List?) {} fun openGalleryForImage() { - val intent = Intent(Intent.ACTION_PICK) - intent.type = "image/*" - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multipleImage) - startActivityForResult(intent, IMAGE_SELECT_REQUEST_CODE) + permissionRequest.launch(multipleImage) } + private val permissionRequest = registerForActivityResult(getResultsContract()) { result -> + @Suppress("UNCHECKED_CAST") + when (result) { + is Uri -> onImageGalleryResult(result) + is List<*> -> onImageGalleryResult(result as List) + } + } + + private fun getResultsContract(): ActivityResultContract { + return object : ActivityResultContract() { + override fun createIntent(context: Context, input: Boolean): Intent { + return Intent(Intent.ACTION_GET_CONTENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, input) + .setType("image/*") + } + + override fun parseResult(resultCode: Int, intent: Intent?): Any? { + intent?.clipData?.takeIf { it.itemCount > 1 }?.convertToList()?.let { clip -> + val list = clip.takeIf { it.size > 10 }?.let { + clip.subList(0, 9) + } ?: clip + return list + } + return intent?.data + } + } + } + + fun showToast(message: String) = (activity as BaseActivity<*, *>).showToast(message) + fun showSnackBar(message: String) = (activity as BaseActivity<*, *>).showSnackBar(message) } \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/base/BaseViewModel.kt b/app/src/main/java/h_mal/appttude/com/driver/base/BaseViewModel.kt index e94fe99..f32a029 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/base/BaseViewModel.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/base/BaseViewModel.kt @@ -20,6 +20,9 @@ abstract class BaseViewModel : ViewModel() { uiState.postValue(ViewState.HasError(Event(error))) } + /* + * All in one function for trying an operation and handling its start and failure + */ suspend fun doTryOperation( defaultErrorMessage: String?, operation: suspend () -> Unit @@ -29,14 +32,11 @@ abstract class BaseViewModel : ViewModel() { operation() } catch (e: Exception) { e.printStackTrace() - e.message?.let { - onError(it) - return - } defaultErrorMessage?.let { onError(it) return } + onError((e.message ?: "Operation failed!!")) } } } \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/base/DataSubmissionBaseViewModel.kt b/app/src/main/java/h_mal/appttude/com/driver/base/DataSubmissionBaseViewModel.kt index 5e3793c..d962e18 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/base/DataSubmissionBaseViewModel.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/base/DataSubmissionBaseViewModel.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import java.io.IOException + abstract class DataSubmissionBaseViewModel( auth: FirebaseAuthentication, private val database: FirebaseDatabaseSource, @@ -32,7 +33,7 @@ abstract class DataSubmissionBaseViewModel( open fun setDataInDatabase(data: T, localImageUris: List?): Job = Job() open fun setDataInDatabase(data: T) {} - inline fun getDataClass() = io { + inline fun retrieveDataFromDatabase() = io { doTryOperation("Failed to retrieve $objectName") { val data = databaseRef.getDataFromDatabaseRef() onSuccess(data ?: FirebaseCompletion.Default) diff --git a/app/src/main/java/h_mal/appttude/com/driver/base/DrawerActivity.kt b/app/src/main/java/h_mal/appttude/com/driver/base/DrawerActivity.kt index ef12248..22194a2 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/base/DrawerActivity.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/base/DrawerActivity.kt @@ -48,7 +48,6 @@ abstract class DrawerActivity : BaseActivit appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout) navView.setupWithNavController(navController) setupActionBarWithNavController(navController, appBarConfiguration) - } override fun onSupportNavigateUp(): Boolean { diff --git a/app/src/main/java/h_mal/appttude/com/driver/data/FirebaseAuthSource.kt b/app/src/main/java/h_mal/appttude/com/driver/data/FirebaseAuthSource.kt index 8820572..dec0995 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/data/FirebaseAuthSource.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/data/FirebaseAuthSource.kt @@ -2,7 +2,11 @@ package h_mal.appttude.com.driver.data import android.net.Uri import com.google.android.gms.tasks.Task -import com.google.firebase.auth.* +import com.google.firebase.auth.AuthResult +import com.google.firebase.auth.EmailAuthProvider +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.FirebaseUser +import com.google.firebase.auth.UserProfileChangeRequest import java.io.IOException class FirebaseAuthSource : FirebaseAuthentication { diff --git a/app/src/main/java/h_mal/appttude/com/driver/data/FirebaseDatabaseSource.kt b/app/src/main/java/h_mal/appttude/com/driver/data/FirebaseDatabaseSource.kt index 9b798d2..adbeee4 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/data/FirebaseDatabaseSource.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/data/FirebaseDatabaseSource.kt @@ -21,6 +21,7 @@ const val PRIVATE_HIRE_VEHICLE = "private_hire_vehicle" const val VEHICLE_DETAILS = "vehicle_details" const val ARCHIVE = "archive" +@SuppressWarnings("unused|WeakerAccess") class FirebaseDatabaseSource { private val database = FirebaseDatabase.getInstance() @@ -35,11 +36,17 @@ class FirebaseDatabaseSource { return data } - fun getUserRef(uid: String) = database.getReference(USER_CONST).child(uid) + fun getDatabaseRefFromPath(path: String) = database.getReference(path) + + val users = database.getReference(USER_CONST) + + fun getUsersRef() = database.reference.child(USER_CONST) + fun getUserRef(uid: String) = users.child(uid) fun getUserDetailsRef(uid: String) = getUserRef(uid).child(USER_DETAILS) fun getVehicleRef(uid: String) = getUserRef(uid).child(VEHICLE_PROFILE) fun getDriverRef(uid: String) = getUserRef(uid).child(DRIVER_PROFILE) fun getApprovalsRef(uid: String) = getUserRef(uid).child(APPROVALS) + fun getDocumentApprovalRef(uid: String, document: String) = getApprovalsRef(uid).child(document) fun getArchiveRef(uid: String) = getUserRef(uid).child(ARCHIVE) fun getUserRoleRef(uid: String) = getUserRef(uid).child(PROFILE_ROLE) fun getDriverNumberRef(uid: String) = getUserRef(uid).child(DRIVER_NUMBER) diff --git a/app/src/main/java/h_mal/appttude/com/driver/data/Roles.kt b/app/src/main/java/h_mal/appttude/com/driver/data/Roles.kt index 4231fee..3dd629e 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/data/Roles.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/data/Roles.kt @@ -1,4 +1,4 @@ package h_mal.appttude.com.driver.data const val DRIVER = "driver" -const val ADMIN = "super_user" \ No newline at end of file +const val ADMIN = "admin" \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/data/prefs/PreferencesProvider.kt b/app/src/main/java/h_mal/appttude/com/driver/data/prefs/PreferencesProvider.kt new file mode 100644 index 0000000..b017b28 --- /dev/null +++ b/app/src/main/java/h_mal/appttude/com/driver/data/prefs/PreferencesProvider.kt @@ -0,0 +1,29 @@ +package h_mal.appttude.com.driver.data.prefs + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager + +/** + * Shared prefs class used for storing conversion name values as pairs + * Then retrieving as pairs + * + */ +const val SORT_OPTION = "SORT_OPTION" +class PreferenceProvider (context: Context) { + + private val appContext = context.applicationContext + + private val preference: SharedPreferences = + PreferenceManager.getDefaultSharedPreferences(appContext) + + fun setSortOption(sortLabel: String) { + preference.edit() + .putString(SORT_OPTION, sortLabel) + .apply() + } + + fun getSortOption(): String? = preference + .getString(SORT_OPTION, null) + +} \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/ui/update/DeleteProfileFragment.kt b/app/src/main/java/h_mal/appttude/com/driver/ui/update/DeleteProfileFragment.kt index 1402ff2..2015bf7 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/ui/update/DeleteProfileFragment.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/ui/update/DeleteProfileFragment.kt @@ -12,7 +12,7 @@ class DeleteProfileFragment : override fun setupView(binding: FragmentDeleteProfileBinding) = binding.run { passwordTop.setEnterPressedListener { deleteUser() } - submissionButtonLabel.setOnClickListener { deleteUser() } + submit.setOnClickListener { deleteUser() } } private fun deleteUser() = applyBinding { diff --git a/app/src/main/java/h_mal/appttude/com/driver/ui/update/UpdateActivity.kt b/app/src/main/java/h_mal/appttude/com/driver/ui/update/UpdateActivity.kt index 71d6691..ea00689 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/ui/update/UpdateActivity.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/ui/update/UpdateActivity.kt @@ -3,7 +3,6 @@ package h_mal.appttude.com.driver.ui.update import h_mal.appttude.com.driver.base.BaseActivity import h_mal.appttude.com.driver.data.FirebaseCompletion import h_mal.appttude.com.driver.databinding.UpdateActivityBinding -import h_mal.appttude.com.driver.utils.displayToast import h_mal.appttude.com.driver.viewmodels.UpdateUserViewModel class UpdateActivity : BaseActivity() { @@ -11,7 +10,7 @@ class UpdateActivity : BaseActivity( override fun onSuccess(data: Any?) { super.onSuccess(data) when (data) { - is FirebaseCompletion.Changed -> displayToast(data.message) + is FirebaseCompletion.Changed -> showToast(data.message) } } } \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/ui/update/UpdateEmailFragment.kt b/app/src/main/java/h_mal/appttude/com/driver/ui/update/UpdateEmailFragment.kt index c0e1ef2..fe7e051 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/ui/update/UpdateEmailFragment.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/ui/update/UpdateEmailFragment.kt @@ -12,7 +12,7 @@ class UpdateEmailFragment : BaseFragment Context.createIntent(): Intent = - Intent(this, T::class.java) - - inline fun Fragment.createIntent() = - requireContext().createIntent() - -} \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/utils/DateUtils.kt b/app/src/main/java/h_mal/appttude/com/driver/utils/DateUtils.kt index ed2d5bd..c8fef1f 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/utils/DateUtils.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/utils/DateUtils.kt @@ -6,10 +6,6 @@ import java.text.SimpleDateFormat import java.util.* object DateUtils { - fun getDateStamp(): String { - val sdf: SimpleDateFormat = getSimpleDateFormat("yyyyMMdd_HHmm") - return sdf.format(Date()) - } fun getDateTimeStamp(): String { val sdf: SimpleDateFormat = getSimpleDateFormat("yyyyMMdd_HHmmss") @@ -21,7 +17,7 @@ object DateUtils { return try { val sdfIn = getSimpleDateFormat(formatIn) val sdfOut = getSimpleDateFormat(formatOut) - val newDate: Date = sdfIn.parse(this) + val newDate = sdfIn.parse(this) sdfOut.format(newDate) } catch (e: Exception) { e.printStackTrace() diff --git a/app/src/main/java/h_mal/appttude/com/driver/utils/Extensions.kt b/app/src/main/java/h_mal/appttude/com/driver/utils/Extensions.kt index d509275..fbd64b5 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/utils/Extensions.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/utils/Extensions.kt @@ -1,13 +1,22 @@ package h_mal.appttude.com.driver.utils - +/** + * Extension function that will execute high order if value is true or do nothing + * + * @sample #boolean.isTrue{ #Do something when its true } + */ inline fun Boolean.isTrue(block: () -> Unit){ if (this) { block() } } -inline fun T.isNotNull(block: (T) -> R): R?{ +/** + * Extension function that will execute high order if value is not null or do nothing + * + * @sample #nullable.isNotNull{i -> i.doSomethingSinceItsNotNull() } + */ +inline fun T?.isNotNull(block: (T) -> R): R?{ return if (this != null) { block(this) } else { diff --git a/app/src/main/java/h_mal/appttude/com/driver/utils/FirebaseException.kt b/app/src/main/java/h_mal/appttude/com/driver/utils/FirebaseException.kt new file mode 100644 index 0000000..5949f2a --- /dev/null +++ b/app/src/main/java/h_mal/appttude/com/driver/utils/FirebaseException.kt @@ -0,0 +1,55 @@ +package h_mal.appttude.com.driver.utils + +import com.google.firebase.database.DatabaseError + +class FirebaseException( + private val databaseError: DatabaseError +) : RuntimeException(databaseError.message, databaseError.toException()) { + + fun getCode() = databaseError.code + fun getDetails() = databaseError.details + + fun getErrorStatus(): Status { + return Status.getByScore(getCode()) ?: Status.UNKNOWN_ERROR + } + + enum class Status(private val code: Int) { + DATA_STALE(-1), + /** The server indicated that this operation failed */ + OPERATION_FAILED(-2), + /** This client does not have permission to perform this operation */ + PERMISSION_DENIED(-3), + /** The operation had to be aborted due to a network disconnect */ + DISCONNECTED(-4), + /** The supplied auth token has expired */ + EXPIRED_TOKEN (-6), + /** + * The specified authentication token is invalid. This can occur when the token is malformed, + * expired, or the secret that was used to generate it has been revoked. + */ + INVALID_TOKEN(-7), + /** The transaction had too many retries */ + MAX_RETRIES(-8), + /** The transaction was overridden by a subsequent set */ + OVERRIDDEN_BY_SET(-9), + /** The service is unavailable */ + UNAVAILABLE(-10), + /** An exception occurred in user code */ + USER_CODE_EXCEPTION(-11), + /** The operation could not be performed due to a network error. */ + NETWORK_ERROR(-24), + /** The write was canceled locally */ + WRITE_CANCELED(-25), + /** + * An unknown error occurred. Please refer to the error message and error details for more + * information. + */ + UNKNOWN_ERROR(-999); + + companion object { + infix fun getByScore(value: Int): Status? = + Status.values().firstOrNull { it.code == value } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/utils/FirebaseUtils.kt b/app/src/main/java/h_mal/appttude/com/driver/utils/FirebaseUtils.kt index 98d7b9a..00b35bf 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/utils/FirebaseUtils.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/utils/FirebaseUtils.kt @@ -30,7 +30,6 @@ suspend fun DatabaseReference.singleValueEvent(): EventResponse = suspendCorouti /** * Read database reference once {@link #DatabaseReference.addListenerForSingleValueEvent} * - * * @return T */ suspend inline fun DatabaseReference.getDataFromDatabaseRef(): T? { @@ -39,7 +38,34 @@ suspend inline fun DatabaseReference.getDataFromDatabaseRef(): response.snapshot.getValue(T::class.java) } is EventResponse.Cancelled -> { - throw response.error.toException() + throw FirebaseException(response.error) + } + } +} + +/** + * Read database reference once {@link #DatabaseReference.addListenerForSingleValueEvent} + * + * @return T + */ +suspend inline fun DatabaseReference.getListDataFromDatabaseRef(): List { + return when (val response: EventResponse = singleValueEvent()) { + is EventResponse.Changed -> { + response.snapshot.children.map { it.getValue(T::class.java) } + } + is EventResponse.Cancelled -> { + throw FirebaseException(response.error) + } + } +} + +suspend fun DatabaseReference.getDataFromDatabaseRef(clazz : Class): T? { + return when (val response: EventResponse = singleValueEvent()) { + is EventResponse.Changed -> { + response.snapshot.getValue(clazz) + } + is EventResponse.Cancelled -> { + throw FirebaseException(response.error) } } } \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/utils/GenericsHelper.kt b/app/src/main/java/h_mal/appttude/com/driver/utils/GenericsHelper.kt new file mode 100644 index 0000000..24238dd --- /dev/null +++ b/app/src/main/java/h_mal/appttude/com/driver/utils/GenericsHelper.kt @@ -0,0 +1,52 @@ +package h_mal.appttude.com.driver.utils + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import java.lang.reflect.ParameterizedType +import kotlin.reflect.KClass + +object GenericsHelper { + @Suppress("UNCHECKED_CAST") + fun Any.getGenericClassAt(position: Int): KClass = + ((javaClass.genericSuperclass as? ParameterizedType) + ?.actualTypeArguments?.getOrNull(position) as? Class) + ?.kotlin + ?: throw IllegalStateException("Can not find class from generic argument") + + /** + * Create a view binding out of the the generic [VB] + * + * @sample inflateBindingByType(getGenericClassAt(0), layoutInflater) + */ + fun inflateBindingByType( + genericClassAt: KClass, + layoutInflater: LayoutInflater + ): VB = try { + @Suppress("UNCHECKED_CAST") + + genericClassAt.java.methods.first { viewBinding -> + viewBinding.parameterTypes.size == 1 + && viewBinding.parameterTypes.getOrNull(0) == LayoutInflater::class.java + }.invoke(null, layoutInflater) as VB + } catch (exception: Exception) { + println ("generic class failed at = $genericClassAt") + exception.printStackTrace() + throw IllegalStateException("Can not inflate binding from generic") + } + + fun LayoutInflater.inflateBindingByType( + container: ViewGroup?, + genericClassAt: KClass + ): VB = try { + @Suppress("UNCHECKED_CAST") + genericClassAt.java.methods.first { inflateFun -> + inflateFun.parameterTypes.size == 3 + && inflateFun.parameterTypes.getOrNull(0) == LayoutInflater::class.java + && inflateFun.parameterTypes.getOrNull(1) == ViewGroup::class.java + && inflateFun.parameterTypes.getOrNull(2) == Boolean::class.java + }.invoke(null, this, container, false) as VB + } catch (exception: Exception) { + throw IllegalStateException("Can not inflate binding from generic") + } +} \ No newline at end of file diff --git a/app/src/main/java/h_mal/appttude/com/driver/utils/NavigationUtils.kt b/app/src/main/java/h_mal/appttude/com/driver/utils/NavigationUtils.kt index c48adf2..1108246 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/utils/NavigationUtils.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/utils/NavigationUtils.kt @@ -1,28 +1,29 @@ package h_mal.appttude.com.driver.utils -import android.content.Context -import android.content.Intent +import android.os.Bundle +import android.os.Parcelable import android.view.View import androidx.annotation.IdRes import androidx.navigation.Navigation -const val UPLOAD_NEW = "upload_new" +fun View.navigateTo(@IdRes navId: Int, args: Bundle? = null) { + Navigation + .findNavController(this) + .navigate(navId, args) +} + +fun Any.toBundle(key: String): Bundle { + return Bundle().apply { + when (this@toBundle) { + is String -> putString(key, this@toBundle) + is Int -> putInt(key, this@toBundle) + is Boolean -> putBoolean(key, this@toBundle) + is Parcelable -> putParcelable(key, this@toBundle) + is Double -> putDouble(key, this@toBundle) + is Float -> putFloat(key, this@toBundle) + } -fun navigateToActivity(context: Context, navigationActivity: Navigations) { - try { - val ourClass: Class<*> = - Class.forName("h_mal.appttude.com.driver." + navigationActivity.value) - val intent = Intent(context, ourClass) - context.startActivity(intent) - } catch (e: Exception) { - e.printStackTrace() } } -fun View.navigateTo(@IdRes navId: Int) { - Navigation - .findNavController(this) - .navigate(navId) -} - diff --git a/app/src/main/java/h_mal/appttude/com/driver/utils/ViewUtils.kt b/app/src/main/java/h_mal/appttude/com/driver/utils/ViewUtils.kt index febf32d..ed50c3d 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/utils/ViewUtils.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/utils/ViewUtils.kt @@ -2,7 +2,6 @@ package h_mal.appttude.com.driver.utils import android.annotation.SuppressLint import android.app.Activity -import android.content.Context import android.content.Intent import android.content.res.Resources import android.graphics.Bitmap @@ -16,7 +15,6 @@ import android.view.inputmethod.InputMethodManager import android.widget.EditText import android.widget.ImageView import android.widget.TextView -import android.widget.Toast import androidx.annotation.DrawableRes import androidx.appcompat.widget.SearchView import androidx.fragment.app.Fragment @@ -33,14 +31,6 @@ fun View.hide() { this.visibility = View.GONE } -fun Context.displayToast(message: String) { - Toast.makeText(this, message, Toast.LENGTH_LONG).show() -} - -fun Fragment.displayToast(message: String) { - requireContext().displayToast(message) -} - fun EditText.setEnterPressedListener(action: () -> Unit) { setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ -> if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) { diff --git a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/RoleViewModel.kt b/app/src/main/java/h_mal/appttude/com/driver/viewmodels/RoleViewModel.kt index 3e3f6df..c0ea4ac 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/viewmodels/RoleViewModel.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/viewmodels/RoleViewModel.kt @@ -18,7 +18,7 @@ class RoleViewModel( override val storageRef: StorageReference? = null override val objectName: String = "role" - override fun getDataFromDatabase() = getDataClass() + override fun getDataFromDatabase() = retrieveDataFromDatabase() override fun setDataInDatabase(data: String) { io { diff --git a/app/src/main/res/drawable-hdpi/cars.png b/app/src/main/res/drawable-hdpi/cars.png deleted file mode 100644 index f45e424..0000000 Binary files a/app/src/main/res/drawable-hdpi/cars.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/splash_screen.png b/app/src/main/res/drawable-hdpi/splash_screen.png new file mode 100644 index 0000000..f707bb5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/splash_screen.png differ diff --git a/app/src/main/res/drawable-ldpi/cars.png b/app/src/main/res/drawable-ldpi/cars.png deleted file mode 100644 index b0b31c7..0000000 Binary files a/app/src/main/res/drawable-ldpi/cars.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/cars.png b/app/src/main/res/drawable-mdpi/cars.png deleted file mode 100644 index c863164..0000000 Binary files a/app/src/main/res/drawable-mdpi/cars.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/splash_screen.png b/app/src/main/res/drawable-mdpi/splash_screen.png new file mode 100644 index 0000000..01c44b4 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/splash_screen.png differ diff --git a/app/src/main/res/drawable-xhdpi/cars.png b/app/src/main/res/drawable-xhdpi/cars.png deleted file mode 100644 index fc40c5e..0000000 Binary files a/app/src/main/res/drawable-xhdpi/cars.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/splash_screen.png b/app/src/main/res/drawable-xhdpi/splash_screen.png new file mode 100644 index 0000000..4abd07a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/splash_screen.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bg.jpg b/app/src/main/res/drawable-xxhdpi/bg.jpg deleted file mode 100644 index 3477474..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/bg.jpg and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/cars.png b/app/src/main/res/drawable-xxhdpi/cars.png deleted file mode 100644 index 248c230..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/cars.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/splash_screen.png b/app/src/main/res/drawable-xxhdpi/splash_screen.png new file mode 100644 index 0000000..a80cb6e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/splash_screen.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/cars.png b/app/src/main/res/drawable-xxxhdpi/cars.png deleted file mode 100644 index bdd868f..0000000 Binary files a/app/src/main/res/drawable-xxxhdpi/cars.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/splash_screen.png b/app/src/main/res/drawable-xxxhdpi/splash_screen.png new file mode 100644 index 0000000..fe8defc Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/splash_screen.png differ diff --git a/app/src/main/res/drawable/baseline_check_24.xml b/app/src/main/res/drawable/baseline_check_24.xml new file mode 100644 index 0000000..2501e9f --- /dev/null +++ b/app/src/main/res/drawable/baseline_check_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/baseline_clear_24.xml b/app/src/main/res/drawable/baseline_clear_24.xml new file mode 100644 index 0000000..70db409 --- /dev/null +++ b/app/src/main/res/drawable/baseline_clear_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/baseline_inbox_24.xml b/app/src/main/res/drawable/baseline_inbox_24.xml new file mode 100644 index 0000000..5857b5d --- /dev/null +++ b/app/src/main/res/drawable/baseline_inbox_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/cardviewoutline.xml b/app/src/main/res/drawable/cardviewoutline.xml deleted file mode 100644 index 9cdbe25..0000000 --- a/app/src/main/res/drawable/cardviewoutline.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/round_edit_text.xml b/app/src/main/res/drawable/round_edit_text.xml deleted file mode 100644 index 744ceb1..0000000 --- a/app/src/main/res/drawable/round_edit_text.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml deleted file mode 100644 index a36d7a1..0000000 --- a/app/src/main/res/drawable/side_nav_bar.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/zero.png b/app/src/main/res/drawable/zero.png deleted file mode 100644 index ceadf26..0000000 Binary files a/app/src/main/res/drawable/zero.png and /dev/null differ diff --git a/app/src/main/res/layout/address_dialog.xml b/app/src/main/res/layout/address_dialog.xml deleted file mode 100644 index 39974a2..0000000 --- a/app/src/main/res/layout/address_dialog.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - -