diff --git a/.circleci/config.yml b/.circleci/config.yml index c9fe671..bd9f168 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,8 +27,15 @@ jobs: # Add steps to the job # See: https://circleci.com/docs/2.0/configuration-reference/#steps steps: - # Checkout the code as the first step. + # Checkout the code and its submodule as the first step. - checkout + # config git user + - 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 # Setup files for build. - run: name: Setup variables for build @@ -50,13 +57,14 @@ jobs: - run: name: Start firebase emulator command: | - firebase emulators:start + firebase emulators:start --import=driver_app_data/export_directory 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 + max-tries: 1 # store test reports - store_artifacts: path: app/build/reports/androidTests/connected diff --git a/.gitignore b/.gitignore index c9415c3..4468893 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /.idea/navEditor.xml /.idea/misc.xml /.idea/assetWizardSettings.xml +/.idea/shelf/ .DS_Store /build /captures @@ -19,3 +20,6 @@ local /.circleci/run_local.bash /Gemfile.lock /playstore.json + +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/app/build.gradle b/app/build.gradle index 4a43c3f..387eda3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -150,7 +150,7 @@ dependencies { implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version" implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version" / * Image Carousal */ - implementation 'com.synnapps:carouselview:0.1.5' + implementation 'com.synnapps:carouselview:0.1.6' / * Glide */ implementation 'com.github.bumptech.glide:glide:4.12.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' 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 index 34fb329..6bb156a 100644 --- 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 @@ -1,5 +1,6 @@ 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 @@ -12,7 +13,8 @@ class ApplicationViewModelFactory( private val auth: FirebaseAuthSource, private val database: FirebaseDatabaseSource, private val storage: FirebaseStorageSource, - private val preferences: PreferenceProvider + private val preferences: PreferenceProvider, + private val resources: Resources ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -57,6 +59,7 @@ class ApplicationViewModelFactory( database, preferences ) + isAssignableFrom(ApproverViewModel::class.java) -> ApproverViewModel(resources , database) else -> throw IllegalArgumentException("Unknown ViewModel class") } as T } 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/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/objects/ArchiveObject.kt b/app/src/admin/java/h_mal/appttude/com/driver/objects/ArchiveObject.kt index c79088f..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,6 +1,12 @@ package h_mal.appttude.com.driver.objects -import h_mal.appttude.com.driver.model.* +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 data class ArchiveObject( 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 1cfec4d..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,6 +1,9 @@ package h_mal.appttude.com.driver.objects.wholeObject -import h_mal.appttude.com.driver.model.* +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.PrivateHireVehicle import h_mal.appttude.com.driver.model.VehicleProfile data class VehicleProfile ( 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 d08f1a2..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,77 +1,57 @@ package h_mal.appttude.com.driver.ui +import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.BaseAdapter -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import h_mal.appttude.com.driver.R +import android.widget.ArrayAdapter 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 layoutInflater: LayoutInflater, - private var approvals: Map, + private val context: Context, + private val data: List>, private val callback: (String) -> Unit -) : BaseAdapter() { - override fun getCount(): Int = approvals.size - override fun getItem(position: Int): Map.Entry = approvals.entries.elementAt(position) - override fun getItemId(position: Int): Long = position.toLong() +) : ArrayAdapter>(context, 0, data) { + + override fun getCount(): Int = data.size override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { var listItemView: View? = convertView val binding: ApprovalListItemBinding if (listItemView == null) { - binding = ApprovalListItemBinding.inflate(layoutInflater, 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 } - val key = getItem(position).key - val itemValue = getItem(position).value + val key: String = getItem(position)?.first ?: throw IOException("No document name provided") + val approvalStatus: ApprovalStatus? = getItem(position)?.second binding.approvalText.text = key - if (itemValue != 0) { - binding.root.setOnClickListener { callback.invoke(key) } - } - binding.approvalIv.setImageResource(getImageResourceBasedOnApproval(itemValue)) - binding.approvalStatus.text = listItemView.context.getString(getStringResourceBasedOnApproval(itemValue)) - - if (position == 0) { - binding.divider.hide() + 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) } - @DrawableRes - private fun getImageResourceBasedOnApproval(value: Int?): Int { - return when(value) { - 0 -> R.drawable.denied - 1 -> R.drawable.denied - 2 -> R.drawable.pending - 3 -> R.drawable.approved - else -> R.drawable.pending - } - } - @StringRes - private fun getStringResourceBasedOnApproval(value: Int?): Int { - return when(value) { - 0 -> R.string.not_submitted - 1 -> R.string.denied - 2 -> R.string.pending - 3 -> R.string.approved - else -> R.string.pending - } - } - - fun updateAdapter(data: Map) { - approvals = data - notifyDataSetChanged() + 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 index d3e85d6..d8df9ae 100644 --- 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 @@ -1,11 +1,12 @@ package h_mal.appttude.com.driver.ui -import android.view.View import android.widget.ListView import h_mal.appttude.com.driver.R import h_mal.appttude.com.driver.base.BaseFragment import h_mal.appttude.com.driver.data.USER_CONST import h_mal.appttude.com.driver.databinding.FragmentUserMainBinding +import h_mal.appttude.com.driver.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 @@ -19,40 +20,30 @@ class DriverOverviewFragment : BaseFragment) { + if (data is List<*>) { + val listData = data as List> if (listView.adapter == null) { - listView.adapter = ApprovalListAdapter(layoutInflater, data as Map) { - this.view?.applyNavigation(it) + 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(data as Map) + (listView.adapter as ApprovalListAdapter).updateAdapter(listData) } } } - - private fun View.applyNavigation(key: String) { - val navId = when (key) { - context.getString(R.string.driver_profile) -> R.id.to_driverProfileFragment - context.getString(R.string.drivers_license) -> R.id.to_driverLicenseFragment - context.getString(R.string.private_hire_license) -> R.id.to_privateHireLicenseFragment - context.getString(R.string.vehicle_profile) -> R.id.to_vehicleProfileFragment - context.getString(R.string.insurance) -> R.id.to_insuranceFragment - context.getString(R.string.m_o_t) -> R.id.to_motFragment - context.getString(R.string.log_book) -> R.id.to_logbookFragment - context.getString(R.string.private_hire_vehicle_license) -> R.id.to_privateHireVehicleFragment - else -> { - throw StringIndexOutOfBoundsException("No resource for $key") - } - } - navigateTo(navId, driverId.toBundle(USER_CONST)) - } } \ 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 f75e34d..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 @@ -19,6 +19,8 @@ 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 @@ -27,7 +29,8 @@ import h_mal.appttude.com.driver.viewmodels.SuperUserViewModel import java.util.* -class HomeSuperUserFragment : BaseFragment(), MenuProvider { +class HomeSuperUserFragment : BaseFragment(), + MenuProvider { private lateinit var adapter: FirebaseRecyclerAdapter> override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -42,6 +45,17 @@ class HomeSuperUserFragment : BaseFragment -> 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) + } + } + } @Suppress("UNCHECKED_CAST") private fun setAdapterToRecyclerView(options: FirebaseRecyclerOptions<*>) { @@ -74,8 +88,8 @@ class HomeSuperUserFragment : BaseFragment): BaseFirebaseAdapter { - return object : BaseFirebaseAdapter(options, layoutInflater) { - + return object : + BaseFirebaseAdapter(options, layoutInflater) { override fun onBindViewHolder( holder: CustomViewHolder, position: Int, @@ -87,7 +101,8 @@ class HomeSuperUserFragment : BaseFragment if (count > 6) context.displayToast("Identifier cannot be larger than 6") } + doOnTextChanged { _, _, count, _ -> if (count > 6) showToast("Identifier cannot be larger than 6") } } val layout = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL 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 index e5d3218..309cab7 100644 --- 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 @@ -12,6 +12,7 @@ class VehicleProfileFragment : override fun setupView(binding: FragmentVehicleSetupBinding) { super.setupView(binding) viewsToHide(binding.submit) + binding.seizedCheckbox.isEnabled = false } override fun setFields(data: VehicleProfile) { 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/DriverOverviewViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/DriverOverviewViewModel.kt index 9a21521..fbda4ad 100644 --- 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 @@ -5,10 +5,12 @@ 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, @@ -37,17 +39,23 @@ class DriverOverviewViewModel( } } - private fun mapApprovalsForView(data: ApprovalsObject): Map { - return mutableMapOf().apply { - put("Driver Profile", data.driver_details_approval) - put("Drivers License", data.driver_license_approval) - put("Private Hire License", data.private_hire_approval) - put("Vehicle Profile", data.vehicle_details_approval) - put("Insurance", data.insurance_details_approval) - put("M.O.T", data.mot_details_approval) - put("Log book", data.log_book_approval) - put("Private Hire Vehicle License", data.ph_car_approval) - }.toMap() + 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/SuperUserViewModel.kt b/app/src/admin/java/h_mal/appttude/com/driver/viewmodels/SuperUserViewModel.kt index 33fa782..9fdda57 100644 --- 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 @@ -22,7 +22,7 @@ class SuperUserViewModel( } fun createFirebaseOptions(sort: SortOption? = null) { - val ref = firebaseDatabaseSource.getUsersRef() + val ref = firebaseDatabaseSource.getUsersRef().orderByChild("role").startAt("driver").endAt("driver") sort?.isNotNull { preferenceProvider.setSortOption(it.label) } @@ -33,7 +33,7 @@ class SuperUserViewModel( // } val options = FirebaseRecyclerOptions.Builder() - .setQuery(ref.orderByKey(), WholeDriverObject::class.java) + .setQuery(ref, WholeDriverObject::class.java) .build() onSuccess(options) @@ -47,7 +47,7 @@ class SuperUserViewModel( onError("No driver identifier provided") return@doTryOperation } - val text = if (input.length > 6) input.substring(0,7) else input + val text = if (input.length > 6) input.substring(0, 7) else input firebaseDatabaseSource.run { postToDatabaseRed(getDriverNumberRef(uid), text) 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..68d156d --- /dev/null +++ b/app/src/admin/res/layout/empty_users_view.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + \ 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..02eda08 --- /dev/null +++ b/app/src/admin/res/layout/fragment_approver.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + \ 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 3831a54..9a8e8a7 100644 --- a/app/src/admin/res/layout/fragment_home_super_user.xml +++ b/app/src/admin/res/layout/fragment_home_super_user.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:id="@+id/container" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".ui.HomeSuperUserFragment"> @@ -28,9 +29,9 @@ - \ No newline at end of file diff --git a/app/src/admin/res/navigation/main_navigation.xml b/app/src/admin/res/navigation/main_navigation.xml index 085ebfa..fe1c29a 100644 --- a/app/src/admin/res/navigation/main_navigation.xml +++ b/app/src/admin/res/navigation/main_navigation.xml @@ -23,68 +23,16 @@ android:id="@+id/userMainFragment" android:name="h_mal.appttude.com.driver.ui.DriverOverviewFragment" android:label="fragment_user_main" - tools:layout="@layout/fragment_user_main" > + tools:layout="@layout/fragment_user_main"> + - - - - - - - + android:id="@+id/to_approverFragment" + app:destination="@id/approverFragment" /> - - - - - - - + android:id="@+id/approverFragment" + android:name="h_mal.appttude.com.driver.ui.ApproverFragment" + android:label="fragment_approver" + tools:layout="@layout/fragment_approver" /> \ No newline at end of file diff --git a/app/src/admin/res/values/strings.xml b/app/src/admin/res/values/strings.xml index b2f051c..dbfce83 100644 --- a/app/src/admin/res/values/strings.xml +++ b/app/src/admin/res/values/strings.xml @@ -1,3 +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 ed6dcc2..7acdd09 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 @@ -24,7 +24,11 @@ import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.Intents.intending import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction -import androidx.test.espresso.matcher.ViewMatchers.* +import 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 @@ -33,6 +37,7 @@ import org.hamcrest.Matcher import org.hamcrest.Matchers import java.io.File +@SuppressWarnings("unused") open class BaseTestRobot { fun fillEditText(resId: Int, text: String?): ViewInteraction = @@ -51,6 +56,9 @@ open class BaseTestRobot { fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction .check(matches(withText(text))) + fun matchText(viewId: Int, textId: Int): ViewInteraction = onView(withId(viewId)) + .check(matches(withText(textId))) + fun matchText(resId: Int, text: String): ViewInteraction = matchText(matchView(resId), text) fun clickListItem(listRes: Int, position: Int) { @@ -60,7 +68,7 @@ open class BaseTestRobot { } fun scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction? { - return onView(withId(recyclerId)) + return matchView(recyclerId) .perform( // scrollTo will fail the test if no item matches. RecyclerViewActions.scrollTo( @@ -69,8 +77,38 @@ open class BaseTestRobot { ) } + 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) { - scrollToRecyclerItem(recyclerId, text)?.perform(click()) + 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) { 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 109c0ab..e33e9cc 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,17 +1,32 @@ package h_mal.appttude.com.driver +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.Espresso.onView import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingResource +import androidx.test.espresso.Root import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot -import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import h_mal.appttude.com.driver.base.BaseActivity +import h_mal.appttude.com.driver.helpers.BaseViewAction +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 @@ -23,6 +38,8 @@ open class BaseUiTest>( private lateinit var mActivityScenarioRule: ActivityScenario private var mIdlingResource: IdlingResource? = null + private lateinit var currentActivity: Activity + @Before fun setup() { beforeLaunch() @@ -30,7 +47,7 @@ open class BaseUiTest>( mActivityScenarioRule.onActivity { mIdlingResource = it.getIdlingResource()!! IdlingRegistry.getInstance().register(mIdlingResource) - afterLaunch() + afterLaunch(it) } } @@ -42,7 +59,7 @@ open class BaseUiTest>( } fun getResourceString(@StringRes stringRes: Int): String { - return InstrumentationRegistry.getInstrumentation().targetContext.resources.getString( + return getInstrumentation().targetContext.resources.getString( stringRes ) } @@ -50,7 +67,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) } @@ -58,5 +75,55 @@ open class BaseUiTest>( } open fun beforeLaunch() {} - open fun afterLaunch() {} + 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/FirebaseTest.kt b/app/src/androidTest/java/h_mal/appttude/com/driver/FirebaseTest.kt index 18550f4..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,6 +5,8 @@ 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 @@ -16,7 +18,11 @@ open class FirebaseTest>( 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 companion object { @@ -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 { 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/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..b2e3916 --- /dev/null +++ b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/robots/DriverOverviewRobot.kt @@ -0,0 +1,29 @@ +package h_mal.appttude.com.driver.robots + +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 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())) +} \ 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 index aa749ba..2cd8bfb 100644 --- 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 @@ -8,10 +8,15 @@ 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()) } @@ -39,4 +44,10 @@ class HomeAdminRobot : BaseTestRobot() { // 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..89bfba0 --- /dev/null +++ b/app/src/androidTestAdmin/java/h_mal/appttude/com/driver/tests/DocumentApproverTest.kt @@ -0,0 +1,72 @@ +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 { + clickOnItemAtPosition(0) + } + approver { + clickApprove() + checkToastMessage("Document already approved") + Espresso.pressBack() + } + driverOverview { + clickOnItemAtPosition(2) + } + approver { + clickApprove() + Espresso.pressBack() + } + driverOverview { + matchView(2, getResourceString(R.string.approved)) + } + + // Decline check + driverOverview { + clickOnItemAtPosition(3) + } + approver { + clickDecline() + checkToastMessage("Document already declined") + Espresso.pressBack() + } + driverOverview { + clickOnItemAtPosition(1) + } + approver { + clickDecline() + Espresso.pressBack() + } + driverOverview { + matchView(1, getResourceString(R.string.denied)) + } + } + + @Test + fun loginAsAdmin_verifyNoDocumentForNewDriver() { + homeAdmin { + waitUntilDisplayed() + clickOnItem("fanasid@gmail.com") + } + driverOverview { + 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 index 4b5006f..6606a34 100644 --- 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 @@ -1,6 +1,7 @@ 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 @@ -18,7 +19,17 @@ class UserListTest : FirebaseTest(LoginActivity::class.java) { homeAdmin { clickOnDriverIdentifier("rsaif660@gmail.com") submitDialog("ID45") - waitFor(5000) + } + } + + @Test + fun loginAsUser_unableToSeeDrivers_loggedIn() { + login { + waitFor(1100) + attemptLogin(DRIVER_EMAIL) + } + homeAdmin { + showNoPermissionsDisplay() } } diff --git a/app/src/driver/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt b/app/src/driver/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt index c4cada5..0f81ab1 100644 --- a/app/src/driver/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt +++ b/app/src/driver/java/h_mal/appttude/com/driver/application/ApplicationViewModelFactory.kt @@ -11,8 +11,7 @@ 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 storage: FirebaseStorageSource ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") 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/main/java/h_mal/appttude/com/driver/application/DriverApplication.kt b/app/src/main/java/h_mal/appttude/com/driver/application/BaseApplication.kt similarity index 62% rename from app/src/main/java/h_mal/appttude/com/driver/application/DriverApplication.kt rename to app/src/main/java/h_mal/appttude/com/driver/application/BaseApplication.kt index 9800149..78a4740 100644 --- a/app/src/main/java/h_mal/appttude/com/driver/application/DriverApplication.kt +++ b/app/src/main/java/h_mal/appttude/com/driver/application/BaseApplication.kt @@ -4,24 +4,28 @@ import android.app.Application 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 : Application(), KodeinAware { +open class BaseApplication : Application(), KodeinAware { // Kodein aware to initialise the classes used for DI override val kodein = Kodein.lazy { - import(androidXModule(this@DriverApplication)) + import(parentModule) + import(flavourModule) + } + + val parentModule = Kodein.Module("Parent Module") { + import(androidXModule(this@BaseApplication)) bind() from singleton { FirebaseAuthSource() } bind() from singleton { FirebaseDatabaseSource() } bind() from singleton { FirebaseStorageSource() } - bind() from singleton { PreferenceProvider(this@DriverApplication) } - bind() from provider { ApplicationViewModelFactory(instance(), instance(), instance(), instance()) } + } + + open val flavourModule = Kodein.Module("Flavour") { + import(parentModule) } } \ 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 8a5c0ab..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,27 +1,36 @@ package h_mal.appttude.com.driver.base import android.content.Intent +import android.os.Build import android.os.Bundle 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 -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 @@ -108,7 +117,7 @@ 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() mIdlingResource?.setIdleState(true) } @@ -139,6 +148,44 @@ abstract class BaseActivity : AppCompatActi 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() + } + /** * Only called from test, creates and returns a new [BasicIdlingResource]. */ 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 index a102837..62b13c5 100644 --- 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 @@ -8,15 +8,20 @@ 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): +open class BaseFirebaseAdapter( + options: FirebaseRecyclerOptions, + private val layoutInflater: LayoutInflater +) : FirebaseRecyclerAdapter>(options) { - private val connectivityManager = layoutInflater.context.getSystemService(ConnectivityManager::class.java) as ConnectivityManager + private val connectivityManager = + layoutInflater.context.getSystemService(ConnectivityManager::class.java) as ConnectivityManager private var _binding: VB? = null val binding: VB @@ -32,7 +37,7 @@ open class BaseFirebaseAdapter(options: FirebaseRecycl return CustomViewHolder(requireNotNull(_binding)) } - override fun onBindViewHolder(holder: CustomViewHolder, position: Int, model: T) { } + override fun onBindViewHolder(holder: CustomViewHolder, position: Int, model: T) {} override fun getItemId(position: Int): Long { return snapshots.getSnapshot(position).key?.toByteArray() @@ -50,6 +55,26 @@ open class BaseFirebaseAdapter(options: FirebaseRecycl } 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 +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 ba1d5b6..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 @@ -20,7 +20,6 @@ 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.io.File abstract class BaseFragment : Fragment(), KodeinAware { @@ -43,7 +42,6 @@ abstract class BaseFragment : Fragment(), K multipleImage = true } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -169,4 +167,7 @@ abstract class BaseFragment : Fragment(), K } } } + + 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 7580984..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 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 5d38537..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 @@ -36,7 +36,7 @@ class FirebaseDatabaseSource { return data } - fun getDatabaseReferenceFromLink(link: String) = database.getReferenceFromUrl(link) + fun getDatabaseRefFromPath(path: String) = database.getReference(path) val users = database.getReference(USER_CONST) @@ -46,6 +46,7 @@ class FirebaseDatabaseSource { 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/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/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 c2270d0..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,23 @@ 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) } } } @@ -50,7 +65,7 @@ suspend fun DatabaseReference.getDataFromDatabaseRef(clazz : Class): response.snapshot.getValue(clazz) } is EventResponse.Cancelled -> { - throw response.error.toException() + throw FirebaseException(response.error) } } } \ No newline at end of file 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/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-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/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/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/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/mipmap-hdpi/drawable-hdpi/welcome_background.png b/app/src/main/res/mipmap-hdpi/drawable-hdpi/welcome_background.png deleted file mode 100644 index 847c1e3..0000000 Binary files a/app/src/main/res/mipmap-hdpi/drawable-hdpi/welcome_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/drawable-ldpi/welcome_background.png b/app/src/main/res/mipmap-hdpi/drawable-ldpi/welcome_background.png deleted file mode 100644 index 20445c6..0000000 Binary files a/app/src/main/res/mipmap-hdpi/drawable-ldpi/welcome_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/drawable-mdpi/welcome_background.png b/app/src/main/res/mipmap-hdpi/drawable-mdpi/welcome_background.png deleted file mode 100644 index 910c85f..0000000 Binary files a/app/src/main/res/mipmap-hdpi/drawable-mdpi/welcome_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/drawable-xhdpi/welcome_background.png b/app/src/main/res/mipmap-hdpi/drawable-xhdpi/welcome_background.png deleted file mode 100644 index f269958..0000000 Binary files a/app/src/main/res/mipmap-hdpi/drawable-xhdpi/welcome_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/drawable-xxhdpi/welcome_background.png b/app/src/main/res/mipmap-hdpi/drawable-xxhdpi/welcome_background.png deleted file mode 100644 index fd5cc30..0000000 Binary files a/app/src/main/res/mipmap-hdpi/drawable-xxhdpi/welcome_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/drawable-xxxhdpi/welcome_background.png b/app/src/main/res/mipmap-hdpi/drawable-xxxhdpi/welcome_background.png deleted file mode 100644 index 3316f35..0000000 Binary files a/app/src/main/res/mipmap-hdpi/drawable-xxxhdpi/welcome_background.png and /dev/null differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index de7e047..b955ccf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -102,4 +102,18 @@ Not submitted You have no drivers approval status + Approve + Deny + Decline + No drivers to show + There are no drivers present for your organisation. + You do not have permissions to view + You are not a super user. Contact us to get super user access. + Cannot retrieve data. + Check you are logged in correctly and have a working connection. + No connection + Make you have a valid internet connection. + Authentication has failed + There is a problem with authentication. + Image icon for feedback view. diff --git a/database.rules.json b/database.rules.json index 378f192..fadc7ba 100644 --- a/database.rules.json +++ b/database.rules.json @@ -1,9 +1,17 @@ { "rules": { "user": { - ".read": true, + ".read": "root.child('user').child(auth.uid).child('role').val() == 'admin'", "$user_id": { - ".write": "$user_id === auth.uid" + ".write": "$user_id === auth.uid", + ".read": "$user_id === auth.uid", + "driver_number": { + ".write": "root.child('user').child(auth.uid).child('role').val() == 'admin'", + ".read": "root.child('user').child(auth.uid).child('role').val() == 'admin'" + }, + "approvalsObject": { + ".write": "root.child('user').child(auth.uid).child('role').val() == 'admin'" + } } } }