Merge pull request #20 from hmalik144/admin_app_refactor
Admin app refactor
@@ -27,8 +27,15 @@ jobs:
|
|||||||
# Add steps to the job
|
# Add steps to the job
|
||||||
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
|
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
|
||||||
steps:
|
steps:
|
||||||
# Checkout the code as the first step.
|
# Checkout the code and its submodule as the first step.
|
||||||
- checkout
|
- 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.
|
# Setup files for build.
|
||||||
- run:
|
- run:
|
||||||
name: Setup variables for build
|
name: Setup variables for build
|
||||||
@@ -50,13 +57,14 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Start firebase emulator
|
name: Start firebase emulator
|
||||||
command: |
|
command: |
|
||||||
firebase emulators:start
|
firebase emulators:start --import=driver_app_data/export_directory
|
||||||
background: true
|
background: true
|
||||||
# Then start the emulator and run the Instrumentation tests!
|
# Then start the emulator and run the Instrumentation tests!
|
||||||
- android/start-emulator-and-run-tests:
|
- android/start-emulator-and-run-tests:
|
||||||
post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest
|
post-emulator-launch-assemble-command: ./gradlew assemble<< parameters.flavour >>DebugAndroidTest
|
||||||
test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest
|
test-command: ./gradlew connected<< parameters.flavour >>DebugAndroidTest
|
||||||
system-image: system-images;android-25;google_apis;x86
|
system-image: system-images;android-25;google_apis;x86
|
||||||
|
max-tries: 1
|
||||||
# store test reports
|
# store test reports
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: app/build/reports/androidTests/connected
|
path: app/build/reports/androidTests/connected
|
||||||
|
|||||||
4
.gitignore
vendored
@@ -9,6 +9,7 @@
|
|||||||
/.idea/navEditor.xml
|
/.idea/navEditor.xml
|
||||||
/.idea/misc.xml
|
/.idea/misc.xml
|
||||||
/.idea/assetWizardSettings.xml
|
/.idea/assetWizardSettings.xml
|
||||||
|
/.idea/shelf/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
@@ -19,3 +20,6 @@ local
|
|||||||
/.circleci/run_local.bash
|
/.circleci/run_local.bash
|
||||||
/Gemfile.lock
|
/Gemfile.lock
|
||||||
/playstore.json
|
/playstore.json
|
||||||
|
|
||||||
|
database-debug.log
|
||||||
|
firebase-debug.log
|
||||||
|
|||||||
6
.idea/compiler.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<bytecodeTargetLevel target="17" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
7
.idea/dictionaries/h_mal.xml
generated
@@ -1,7 +0,0 @@
|
|||||||
<component name="ProjectDictionaryState">
|
|
||||||
<dictionary name="h_mal">
|
|
||||||
<words>
|
|
||||||
<w>viewmodel</w>
|
|
||||||
</words>
|
|
||||||
</dictionary>
|
|
||||||
</component>
|
|
||||||
20
.idea/gradle.xml
generated
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
|
||||||
<component name="GradleSettings">
|
|
||||||
<option name="linkedExternalProjectsSettings">
|
|
||||||
<GradleProjectSettings>
|
|
||||||
<option name="testRunner" value="GRADLE" />
|
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
|
||||||
<option name="gradleJvm" value="Android Studio java home" />
|
|
||||||
<option name="modules">
|
|
||||||
<set>
|
|
||||||
<option value="$PROJECT_DIR$" />
|
|
||||||
<option value="$PROJECT_DIR$/app" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</GradleProjectSettings>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
48
.idea/misc.xml
generated
@@ -1,48 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="NullableNotNullManager">
|
|
||||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
|
||||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
|
||||||
<option name="myNullables">
|
|
||||||
<value>
|
|
||||||
<list size="12">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
|
||||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
|
||||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
|
||||||
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
|
||||||
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
|
||||||
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
|
||||||
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
|
||||||
<item index="7" class="java.lang.String" itemvalue="android.annotation.Nullable" />
|
|
||||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
|
||||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
|
||||||
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
|
||||||
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
<option name="myNotNulls">
|
|
||||||
<value>
|
|
||||||
<list size="11">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
|
||||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
|
||||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
|
||||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
|
||||||
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
|
||||||
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
|
||||||
<item index="6" class="java.lang.String" itemvalue="android.annotation.NonNull" />
|
|
||||||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
|
||||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
|
||||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
|
||||||
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17_PREVIEW" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectType">
|
|
||||||
<option name="id" value="Android" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -150,7 +150,7 @@ dependencies {
|
|||||||
implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version"
|
implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version"
|
||||||
implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version"
|
implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version"
|
||||||
/ * Image Carousal */
|
/ * Image Carousal */
|
||||||
implementation 'com.synnapps:carouselview:0.1.5'
|
implementation 'com.synnapps:carouselview:0.1.6'
|
||||||
/ * Glide */
|
/ * Glide */
|
||||||
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package h_mal.appttude.com.driver.application
|
package h_mal.appttude.com.driver.application
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import h_mal.appttude.com.driver.data.FirebaseAuthSource
|
import h_mal.appttude.com.driver.data.FirebaseAuthSource
|
||||||
@@ -12,7 +13,8 @@ class ApplicationViewModelFactory(
|
|||||||
private val auth: FirebaseAuthSource,
|
private val auth: FirebaseAuthSource,
|
||||||
private val database: FirebaseDatabaseSource,
|
private val database: FirebaseDatabaseSource,
|
||||||
private val storage: FirebaseStorageSource,
|
private val storage: FirebaseStorageSource,
|
||||||
private val preferences: PreferenceProvider
|
private val preferences: PreferenceProvider,
|
||||||
|
private val resources: Resources
|
||||||
) : ViewModelProvider.Factory {
|
) : ViewModelProvider.Factory {
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@@ -57,6 +59,7 @@ class ApplicationViewModelFactory(
|
|||||||
database,
|
database,
|
||||||
preferences
|
preferences
|
||||||
)
|
)
|
||||||
|
isAssignableFrom(ApproverViewModel::class.java) -> ApproverViewModel(resources , database)
|
||||||
else -> throw IllegalArgumentException("Unknown ViewModel class")
|
else -> throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
} as T
|
} as T
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
package h_mal.appttude.com.driver.objects
|
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(
|
data class ArchiveObject(
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package h_mal.appttude.com.driver.objects.wholeObject
|
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
|
import h_mal.appttude.com.driver.model.VehicleProfile
|
||||||
|
|
||||||
data class VehicleProfile (
|
data class VehicleProfile (
|
||||||
|
|||||||
@@ -1,77 +1,57 @@
|
|||||||
package h_mal.appttude.com.driver.ui
|
package h_mal.appttude.com.driver.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.BaseAdapter
|
import android.widget.ArrayAdapter
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import h_mal.appttude.com.driver.R
|
|
||||||
import h_mal.appttude.com.driver.databinding.ApprovalListItemBinding
|
import h_mal.appttude.com.driver.databinding.ApprovalListItemBinding
|
||||||
|
import h_mal.appttude.com.driver.model.ApprovalStatus
|
||||||
import h_mal.appttude.com.driver.utils.hide
|
import h_mal.appttude.com.driver.utils.hide
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
||||||
class ApprovalListAdapter(
|
class ApprovalListAdapter(
|
||||||
private val layoutInflater: LayoutInflater,
|
private val context: Context,
|
||||||
private var approvals: Map<String, Int?>,
|
private val data: List<Pair<String, ApprovalStatus>>,
|
||||||
private val callback: (String) -> Unit
|
private val callback: (String) -> Unit
|
||||||
) : BaseAdapter() {
|
) : ArrayAdapter<Pair<String, ApprovalStatus>>(context, 0, data) {
|
||||||
override fun getCount(): Int = approvals.size
|
|
||||||
override fun getItem(position: Int): Map.Entry<String, Int?> = approvals.entries.elementAt(position)
|
override fun getCount(): Int = data.size
|
||||||
override fun getItemId(position: Int): Long = position.toLong()
|
|
||||||
|
|
||||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
var listItemView: View? = convertView
|
var listItemView: View? = convertView
|
||||||
val binding: ApprovalListItemBinding
|
val binding: ApprovalListItemBinding
|
||||||
if (listItemView == null) {
|
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 = binding.root
|
||||||
listItemView.setTag(listItemView.id, binding)
|
listItemView.setTag(listItemView.id, binding)
|
||||||
} else {
|
} else {
|
||||||
|
// cell exists so recycling view
|
||||||
binding = listItemView.getTag(listItemView.id) as ApprovalListItemBinding
|
binding = listItemView.getTag(listItemView.id) as ApprovalListItemBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
val key = getItem(position).key
|
val key: String = getItem(position)?.first ?: throw IOException("No document name provided")
|
||||||
val itemValue = getItem(position).value
|
val approvalStatus: ApprovalStatus? = getItem(position)?.second
|
||||||
|
|
||||||
binding.approvalText.text = key
|
binding.approvalText.text = key
|
||||||
if (itemValue != 0) {
|
approvalStatus?.let { item ->
|
||||||
binding.root.setOnClickListener { callback.invoke(key) }
|
item.score.takeIf { it != 0 }?.let {
|
||||||
}
|
binding.root.setOnClickListener { callback.invoke(key) }
|
||||||
binding.approvalIv.setImageResource(getImageResourceBasedOnApproval(itemValue))
|
}
|
||||||
binding.approvalStatus.text = listItemView.context.getString(getStringResourceBasedOnApproval(itemValue))
|
binding.approvalIv.setImageResource(item.drawableId)
|
||||||
|
binding.approvalStatus.text = context.getString(item.stringId)
|
||||||
if (position == 0) {
|
|
||||||
binding.divider.hide()
|
|
||||||
}
|
}
|
||||||
|
// hide divider for first cell
|
||||||
|
if (position == 0) binding.divider.hide()
|
||||||
|
|
||||||
return (listItemView)
|
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
|
fun updateAdapter(date: List<Pair<String, ApprovalStatus>>) {
|
||||||
private fun getStringResourceBasedOnApproval(value: Int?): Int {
|
clear()
|
||||||
return when(value) {
|
addAll(date)
|
||||||
0 -> R.string.not_submitted
|
|
||||||
1 -> R.string.denied
|
|
||||||
2 -> R.string.pending
|
|
||||||
3 -> R.string.approved
|
|
||||||
else -> R.string.pending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateAdapter(data: Map<String, Int?>) {
|
|
||||||
approvals = data
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package h_mal.appttude.com.driver.ui
|
||||||
|
|
||||||
|
import h_mal.appttude.com.driver.base.BaseFragment
|
||||||
|
import h_mal.appttude.com.driver.databinding.FragmentApproverBinding
|
||||||
|
import h_mal.appttude.com.driver.model.ApprovalStatus
|
||||||
|
import h_mal.appttude.com.driver.viewmodels.ApproverViewModel
|
||||||
|
|
||||||
|
|
||||||
|
class ApproverFragment : BaseFragment<ApproverViewModel, FragmentApproverBinding>() {
|
||||||
|
|
||||||
|
override fun setupView(binding: FragmentApproverBinding) = binding.run {
|
||||||
|
super.setupView(binding)
|
||||||
|
|
||||||
|
val args = requireArguments()
|
||||||
|
viewModel.init(args)
|
||||||
|
|
||||||
|
// Retrieve fragment name argument saved from previous fragment
|
||||||
|
val fragmentClass = viewModel.getFragmentClass()
|
||||||
|
|
||||||
|
childFragmentManager.beginTransaction()
|
||||||
|
.replace(container.id, fragmentClass, args, null)
|
||||||
|
.commitNow()
|
||||||
|
|
||||||
|
approve.setOnClickListener { viewModel.approveDocument() }
|
||||||
|
decline.setOnClickListener { viewModel.declineDocument() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(data: Any?) {
|
||||||
|
super.onSuccess(data)
|
||||||
|
when (data) {
|
||||||
|
ApprovalStatus.APPROVED -> displaySnackBar("approved")
|
||||||
|
ApprovalStatus.DENIED -> displaySnackBar("declined")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun displaySnackBar(status: String) {
|
||||||
|
showSnackBar("Document has been $status")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
package h_mal.appttude.com.driver.ui
|
package h_mal.appttude.com.driver.ui
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
import h_mal.appttude.com.driver.R
|
import h_mal.appttude.com.driver.R
|
||||||
import h_mal.appttude.com.driver.base.BaseFragment
|
import h_mal.appttude.com.driver.base.BaseFragment
|
||||||
import h_mal.appttude.com.driver.data.USER_CONST
|
import h_mal.appttude.com.driver.data.USER_CONST
|
||||||
import h_mal.appttude.com.driver.databinding.FragmentUserMainBinding
|
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.navigateTo
|
||||||
import h_mal.appttude.com.driver.utils.toBundle
|
import h_mal.appttude.com.driver.utils.toBundle
|
||||||
import h_mal.appttude.com.driver.viewmodels.DriverOverviewViewModel
|
import h_mal.appttude.com.driver.viewmodels.DriverOverviewViewModel
|
||||||
@@ -19,40 +20,30 @@ class DriverOverviewFragment : BaseFragment<DriverOverviewViewModel, FragmentUse
|
|||||||
|
|
||||||
override fun setupView(binding: FragmentUserMainBinding) {
|
override fun setupView(binding: FragmentUserMainBinding) {
|
||||||
listView = binding.approvalsList
|
listView = binding.approvalsList
|
||||||
|
}
|
||||||
|
|
||||||
driverId = requireArguments().getString(USER_CONST) ?: throw IOException("No user ID has been passed")
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
driverId = requireArguments().getString(USER_CONST)
|
||||||
|
?: throw IOException("No user ID has been passed")
|
||||||
viewModel.loadDriverApprovals(driverId)
|
viewModel.loadDriverApprovals(driverId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSuccess(data: Any?) {
|
override fun onSuccess(data: Any?) {
|
||||||
super.onSuccess(data)
|
super.onSuccess(data)
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
if (data is Map<*, *>) {
|
if (data is List<*>) {
|
||||||
|
val listData = data as List<Pair<String, ApprovalStatus>>
|
||||||
if (listView.adapter == null) {
|
if (listView.adapter == null) {
|
||||||
listView.adapter = ApprovalListAdapter(layoutInflater, data as Map<String, Int?>) {
|
listView.adapter = ApprovalListAdapter(requireContext(), listData) {
|
||||||
this.view?.applyNavigation(it)
|
this.view?.navigateTo(
|
||||||
|
R.id.to_approverFragment,
|
||||||
|
driverId.toBundle(USER_CONST).apply { putString(FRAGMENT, it) })
|
||||||
}
|
}
|
||||||
listView.isScrollContainer = false
|
listView.isScrollContainer = false
|
||||||
} else {
|
} else {
|
||||||
(listView.adapter as ApprovalListAdapter).updateAdapter(data as Map<String, Int?>)
|
(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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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.data.USER_CONST
|
||||||
import h_mal.appttude.com.driver.databinding.FragmentHomeSuperUserBinding
|
import h_mal.appttude.com.driver.databinding.FragmentHomeSuperUserBinding
|
||||||
import h_mal.appttude.com.driver.databinding.ListItemLayoutBinding
|
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.model.SortOption
|
||||||
import h_mal.appttude.com.driver.objects.UserObject
|
import h_mal.appttude.com.driver.objects.UserObject
|
||||||
import h_mal.appttude.com.driver.objects.WholeDriverObject
|
import h_mal.appttude.com.driver.objects.WholeDriverObject
|
||||||
@@ -27,7 +29,8 @@ import h_mal.appttude.com.driver.viewmodels.SuperUserViewModel
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class HomeSuperUserFragment : BaseFragment<SuperUserViewModel, FragmentHomeSuperUserBinding>(), MenuProvider {
|
class HomeSuperUserFragment : BaseFragment<SuperUserViewModel, FragmentHomeSuperUserBinding>(),
|
||||||
|
MenuProvider {
|
||||||
private lateinit var adapter: FirebaseRecyclerAdapter<WholeDriverObject, CustomViewHolder<ListItemLayoutBinding>>
|
private lateinit var adapter: FirebaseRecyclerAdapter<WholeDriverObject, CustomViewHolder<ListItemLayoutBinding>>
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -42,6 +45,17 @@ class HomeSuperUserFragment : BaseFragment<SuperUserViewModel, FragmentHomeSuper
|
|||||||
is FirebaseRecyclerOptions<*> -> setAdapterToRecyclerView(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
private fun setAdapterToRecyclerView(options: FirebaseRecyclerOptions<*>) {
|
private fun setAdapterToRecyclerView(options: FirebaseRecyclerOptions<*>) {
|
||||||
@@ -74,8 +88,8 @@ class HomeSuperUserFragment : BaseFragment<SuperUserViewModel, FragmentHomeSuper
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createAdapter(options: FirebaseRecyclerOptions<WholeDriverObject>): BaseFirebaseAdapter<WholeDriverObject, ListItemLayoutBinding> {
|
private fun createAdapter(options: FirebaseRecyclerOptions<WholeDriverObject>): BaseFirebaseAdapter<WholeDriverObject, ListItemLayoutBinding> {
|
||||||
return object : BaseFirebaseAdapter<WholeDriverObject, ListItemLayoutBinding>(options, layoutInflater) {
|
return object :
|
||||||
|
BaseFirebaseAdapter<WholeDriverObject, ListItemLayoutBinding>(options, layoutInflater) {
|
||||||
override fun onBindViewHolder(
|
override fun onBindViewHolder(
|
||||||
holder: CustomViewHolder<ListItemLayoutBinding>,
|
holder: CustomViewHolder<ListItemLayoutBinding>,
|
||||||
position: Int,
|
position: Int,
|
||||||
@@ -87,7 +101,8 @@ class HomeSuperUserFragment : BaseFragment<SuperUserViewModel, FragmentHomeSuper
|
|||||||
usernameText.text = userDetails?.profileName
|
usernameText.text = userDetails?.profileName
|
||||||
emailaddressText.text = userDetails?.profileEmail
|
emailaddressText.text = userDetails?.profileEmail
|
||||||
driverNo.run {
|
driverNo.run {
|
||||||
val number = if (model.driver_number.isNullOrBlank()) "#N/A" else model.driver_number
|
val number =
|
||||||
|
if (model.driver_number.isNullOrBlank()) "#N/A" else model.driver_number
|
||||||
text = number
|
text = number
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
getKeyAtPosition(position)?.let { showChangeNumberDialog(number!!, it) }
|
getKeyAtPosition(position)?.let { showChangeNumberDialog(number!!, it) }
|
||||||
@@ -105,10 +120,8 @@ class HomeSuperUserFragment : BaseFragment<SuperUserViewModel, FragmentHomeSuper
|
|||||||
override fun onDataChanged() {
|
override fun onDataChanged() {
|
||||||
super.onDataChanged()
|
super.onDataChanged()
|
||||||
applyBinding {
|
applyBinding {
|
||||||
// If there are no chat messages, show a view that invites the user to add a message.
|
// If there are no driver data, show a view that informs the admin.
|
||||||
if (itemCount == 0) {
|
emptyView.root.visibility = if (itemCount == 0) View.VISIBLE else View.GONE
|
||||||
emptyView.root.visibility = if (itemCount == 0) View.VISIBLE else View.GONE
|
|
||||||
}
|
|
||||||
progressCircular.hide()
|
progressCircular.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,8 +136,24 @@ class HomeSuperUserFragment : BaseFragment<SuperUserViewModel, FragmentHomeSuper
|
|||||||
applyBinding { progressCircular.hide() }
|
applyBinding { progressCircular.hide() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun connectionLost() {
|
override fun authorizationError() {
|
||||||
requireContext().displayToast("No connection available")
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +163,7 @@ class HomeSuperUserFragment : BaseFragment<SuperUserViewModel, FragmentHomeSuper
|
|||||||
setTag(R.string.driver_identifier, "DriverIdentifierInput")
|
setTag(R.string.driver_identifier, "DriverIdentifierInput")
|
||||||
setText(defaultNumber)
|
setText(defaultNumber)
|
||||||
setSelectAllOnFocus(true)
|
setSelectAllOnFocus(true)
|
||||||
doOnTextChanged { _, _, count, _ -> 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 {
|
val layout = LinearLayout(context).apply {
|
||||||
orientation = LinearLayout.VERTICAL
|
orientation = LinearLayout.VERTICAL
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class VehicleProfileFragment :
|
|||||||
override fun setupView(binding: FragmentVehicleSetupBinding) {
|
override fun setupView(binding: FragmentVehicleSetupBinding) {
|
||||||
super.setupView(binding)
|
super.setupView(binding)
|
||||||
viewsToHide(binding.submit)
|
viewsToHide(binding.submit)
|
||||||
|
binding.seizedCheckbox.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setFields(data: VehicleProfile) {
|
override fun setFields(data: VehicleProfile) {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package h_mal.appttude.com.driver.utils
|
||||||
|
|
||||||
|
const val FRAGMENT = "fragment"
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package h_mal.appttude.com.driver.viewmodels
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.google.firebase.database.DatabaseReference
|
||||||
|
import h_mal.appttude.com.driver.R
|
||||||
|
import h_mal.appttude.com.driver.base.BaseViewModel
|
||||||
|
import h_mal.appttude.com.driver.data.FirebaseCompletion
|
||||||
|
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
|
||||||
|
import h_mal.appttude.com.driver.data.USER_CONST
|
||||||
|
import h_mal.appttude.com.driver.model.ApprovalStatus
|
||||||
|
import h_mal.appttude.com.driver.objects.ApprovalsObject
|
||||||
|
import h_mal.appttude.com.driver.ui.driverprofile.DriverLicenseFragment
|
||||||
|
import h_mal.appttude.com.driver.ui.driverprofile.DriverProfileFragment
|
||||||
|
import h_mal.appttude.com.driver.ui.driverprofile.PrivateHireLicenseFragment
|
||||||
|
import h_mal.appttude.com.driver.ui.vehicleprofile.InsuranceFragment
|
||||||
|
import h_mal.appttude.com.driver.ui.vehicleprofile.LogbookFragment
|
||||||
|
import h_mal.appttude.com.driver.ui.vehicleprofile.MotFragment
|
||||||
|
import h_mal.appttude.com.driver.ui.vehicleprofile.PrivateHireVehicleFragment
|
||||||
|
import h_mal.appttude.com.driver.ui.vehicleprofile.VehicleProfileFragment
|
||||||
|
import h_mal.appttude.com.driver.utils.Coroutines.io
|
||||||
|
import h_mal.appttude.com.driver.utils.FRAGMENT
|
||||||
|
import h_mal.appttude.com.driver.utils.getDataFromDatabaseRef
|
||||||
|
|
||||||
|
class ApproverViewModel(
|
||||||
|
private val resources: Resources,
|
||||||
|
private val database: FirebaseDatabaseSource
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
private lateinit var name: String
|
||||||
|
private lateinit var docRef: DatabaseReference
|
||||||
|
|
||||||
|
private var score: ApprovalStatus? = null
|
||||||
|
|
||||||
|
fun init(args: Bundle) {
|
||||||
|
// Retried uid & fragment class name from args
|
||||||
|
val uid = args.getString(USER_CONST) ?: throw NullPointerException("No user Id was passed")
|
||||||
|
name = args.getString(FRAGMENT)
|
||||||
|
?: throw NullPointerException("No fragment name argument passed")
|
||||||
|
// Define a document name based on fragment class name
|
||||||
|
val documentName = when (name) {
|
||||||
|
resources.getString(R.string.driver_profile) -> ApprovalsObject::driver_details_approval.name
|
||||||
|
resources.getString(R.string.drivers_license) -> ApprovalsObject::driver_license_approval.name
|
||||||
|
resources.getString(R.string.private_hire_license) -> ApprovalsObject::private_hire_approval.name
|
||||||
|
resources.getString(R.string.vehicle_profile) -> ApprovalsObject::vehicle_details_approval.name
|
||||||
|
resources.getString(R.string.insurance) -> ApprovalsObject::insurance_details_approval.name
|
||||||
|
resources.getString(R.string.m_o_t) -> ApprovalsObject::mot_details_approval.name
|
||||||
|
resources.getString(R.string.log_book) -> ApprovalsObject::log_book_approval.name
|
||||||
|
resources.getString(R.string.private_hire_vehicle_license) -> ApprovalsObject::ph_car_approval.name
|
||||||
|
else -> {
|
||||||
|
throw StringIndexOutOfBoundsException("No resource for $name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
docRef = database.getDocumentApprovalRef(uid, documentName)
|
||||||
|
io {
|
||||||
|
doTryOperation("") {
|
||||||
|
val data = docRef.getDataFromDatabaseRef<Int>()
|
||||||
|
score = data?.let { ApprovalStatus.getByScore(it) } ?: ApprovalStatus.NOT_SUBMITTED
|
||||||
|
onSuccess(FirebaseCompletion.Default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFragmentClass(): Class<out Fragment> {
|
||||||
|
return when (name) {
|
||||||
|
resources.getString(R.string.driver_profile) -> DriverProfileFragment::class.java
|
||||||
|
resources.getString(R.string.drivers_license) -> DriverLicenseFragment::class.java
|
||||||
|
resources.getString(R.string.private_hire_license) -> PrivateHireLicenseFragment::class.java
|
||||||
|
resources.getString(R.string.vehicle_profile) -> VehicleProfileFragment::class.java
|
||||||
|
resources.getString(R.string.insurance) -> InsuranceFragment::class.java
|
||||||
|
resources.getString(R.string.m_o_t) -> MotFragment::class.java
|
||||||
|
resources.getString(R.string.log_book) -> LogbookFragment::class.java
|
||||||
|
resources.getString(R.string.private_hire_vehicle_license) -> PrivateHireVehicleFragment::class.java
|
||||||
|
else -> {
|
||||||
|
throw StringIndexOutOfBoundsException("No resource for $name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun approveDocument() {
|
||||||
|
updateDocument(ApprovalStatus.APPROVED)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun declineDocument() {
|
||||||
|
updateDocument(ApprovalStatus.DENIED)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDocument(approval: ApprovalStatus) {
|
||||||
|
if (approval == score) {
|
||||||
|
val result = if (approval == ApprovalStatus.APPROVED) "approved" else "declined"
|
||||||
|
onError("Document already $result")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
io {
|
||||||
|
doTryOperation("Failed to decline document") {
|
||||||
|
database.postToDatabaseRed(docRef, approval.score)
|
||||||
|
onSuccess(approval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,12 @@ import com.google.firebase.storage.StorageReference
|
|||||||
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
|
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
|
||||||
import h_mal.appttude.com.driver.data.FirebaseAuthentication
|
import h_mal.appttude.com.driver.data.FirebaseAuthentication
|
||||||
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
|
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.objects.ApprovalsObject
|
||||||
import h_mal.appttude.com.driver.utils.Coroutines.io
|
import h_mal.appttude.com.driver.utils.Coroutines.io
|
||||||
import h_mal.appttude.com.driver.utils.getDataFromDatabaseRef
|
import h_mal.appttude.com.driver.utils.getDataFromDatabaseRef
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
class DriverOverviewViewModel(
|
class DriverOverviewViewModel(
|
||||||
auth: FirebaseAuthentication,
|
auth: FirebaseAuthentication,
|
||||||
@@ -37,17 +39,23 @@ class DriverOverviewViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mapApprovalsForView(data: ApprovalsObject): Map<String, Int> {
|
private fun mapApprovalsForView(data: ApprovalsObject): List<Pair<String, ApprovalStatus>> {
|
||||||
return mutableMapOf<String, Int>().apply {
|
val list = mutableListOf<Pair<String, ApprovalStatus>>()
|
||||||
put("Driver Profile", data.driver_details_approval)
|
return list.apply {
|
||||||
put("Drivers License", data.driver_license_approval)
|
add(0, Pair("Driver Profile", getApprovalStatusByScore(data.driver_details_approval)))
|
||||||
put("Private Hire License", data.private_hire_approval)
|
add(1, Pair("Drivers License", getApprovalStatusByScore(data.driver_license_approval)))
|
||||||
put("Vehicle Profile", data.vehicle_details_approval)
|
add(2, Pair("Private Hire License", getApprovalStatusByScore(data.private_hire_approval)))
|
||||||
put("Insurance", data.insurance_details_approval)
|
add(3, Pair("Vehicle Profile", getApprovalStatusByScore(data.vehicle_details_approval)))
|
||||||
put("M.O.T", data.mot_details_approval)
|
add(4, Pair("Insurance", getApprovalStatusByScore(data.insurance_details_approval)))
|
||||||
put("Log book", data.log_book_approval)
|
add(5, Pair("M.O.T", getApprovalStatusByScore(data.mot_details_approval)))
|
||||||
put("Private Hire Vehicle License", data.ph_car_approval)
|
add(6, Pair("Log book", getApprovalStatusByScore(data.log_book_approval)))
|
||||||
}.toMap()
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class SuperUserViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createFirebaseOptions(sort: SortOption? = null) {
|
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) }
|
sort?.isNotNull { preferenceProvider.setSortOption(it.label) }
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ class SuperUserViewModel(
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
val options = FirebaseRecyclerOptions.Builder<WholeDriverObject>()
|
val options = FirebaseRecyclerOptions.Builder<WholeDriverObject>()
|
||||||
.setQuery(ref.orderByKey(), WholeDriverObject::class.java)
|
.setQuery(ref, WholeDriverObject::class.java)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
onSuccess(options)
|
onSuccess(options)
|
||||||
@@ -47,7 +47,7 @@ class SuperUserViewModel(
|
|||||||
onError("No driver identifier provided")
|
onError("No driver identifier provided")
|
||||||
return@doTryOperation
|
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 {
|
firebaseDatabaseSource.run {
|
||||||
postToDatabaseRed(getDriverNumberRef(uid), text)
|
postToDatabaseRed(getDriverNumberRef(uid), text)
|
||||||
|
|||||||
54
app/src/admin/res/layout/empty_users_view.xml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/background_with_curve"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:src="@drawable/splash_screen"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:paddingTop="@dimen/default_indicator_margin_horizontal"
|
||||||
|
android:layout_marginTop="@dimen/default_indicator_margin_horizontal"
|
||||||
|
app:layout_constraintHeight_percent="0.5"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:importantForAccessibility="no" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
style="@style/constraint_container">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="80dp"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/header"
|
||||||
|
android:src="@drawable/baseline_inbox_24"
|
||||||
|
android:contentDescription="@string/image_icon_for_feedback_view" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/header"
|
||||||
|
style="@style/headerStyle"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/no_drivers_to_show"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subtext"
|
||||||
|
style="@style/subheader"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/header"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/no_drivers_subtext"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
51
app/src/admin/res/layout/fragment_approver.xml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
tools:context=".ui.ApproverFragment">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/buttonContainer"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:layout="@layout/fragment_driver_license" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/buttonContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="RtlHardcoded">
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/approve"
|
||||||
|
android:layout_marginLeft="12dp"
|
||||||
|
android:layout_marginRight="6dp"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
style="@style/TextButton.WithIcon"
|
||||||
|
app:icon="@drawable/baseline_check_24"
|
||||||
|
android:text="@string/approve" />
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/decline"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginLeft="6dp"
|
||||||
|
android:layout_marginRight="12dp"
|
||||||
|
style="@style/TextButton.WithIcon"
|
||||||
|
app:icon="@drawable/baseline_clear_24"
|
||||||
|
app:iconTint="@android:color/holo_red_light"
|
||||||
|
android:backgroundTint="@color/colour_ten"
|
||||||
|
android:text="@string/decline" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/container"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context=".ui.HomeSuperUserFragment">
|
tools:context=".ui.HomeSuperUserFragment">
|
||||||
|
|
||||||
@@ -28,9 +29,9 @@
|
|||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/empty_view"
|
android:id="@+id/empty_view"
|
||||||
layout="@layout/empty_layout"
|
layout="@layout/empty_users_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
@@ -38,5 +39,4 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible"/>
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -23,68 +23,16 @@
|
|||||||
android:id="@+id/userMainFragment"
|
android:id="@+id/userMainFragment"
|
||||||
android:name="h_mal.appttude.com.driver.ui.DriverOverviewFragment"
|
android:name="h_mal.appttude.com.driver.ui.DriverOverviewFragment"
|
||||||
android:label="fragment_user_main"
|
android:label="fragment_user_main"
|
||||||
tools:layout="@layout/fragment_user_main" >
|
tools:layout="@layout/fragment_user_main">
|
||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/to_driverLicenseFragment"
|
android:id="@+id/to_approverFragment"
|
||||||
app:destination="@id/driverLicenseFragment" />
|
app:destination="@id/approverFragment" />
|
||||||
<action
|
|
||||||
android:id="@+id/to_driverProfileFragment"
|
|
||||||
app:destination="@id/driverProfileFragment" />
|
|
||||||
<action
|
|
||||||
android:id="@+id/to_privateHireLicenseFragment"
|
|
||||||
app:destination="@id/privateHireLicenseFragment" />
|
|
||||||
<action
|
|
||||||
android:id="@+id/to_insuranceFragment"
|
|
||||||
app:destination="@id/insuranceFragment" />
|
|
||||||
<action
|
|
||||||
android:id="@+id/to_logbookFragment"
|
|
||||||
app:destination="@id/logbookFragment" />
|
|
||||||
<action
|
|
||||||
android:id="@+id/to_motFragment"
|
|
||||||
app:destination="@id/motFragment" />
|
|
||||||
<action
|
|
||||||
android:id="@+id/to_privateHireVehicleFragment"
|
|
||||||
app:destination="@id/privateHireVehicleFragment" />
|
|
||||||
<action
|
|
||||||
android:id="@+id/to_vehicleProfileFragment"
|
|
||||||
app:destination="@id/vehicleProfileFragment" />
|
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/driverLicenseFragment"
|
android:id="@+id/approverFragment"
|
||||||
android:name="h_mal.appttude.com.driver.ui.driverprofile.DriverLicenseFragment"
|
android:name="h_mal.appttude.com.driver.ui.ApproverFragment"
|
||||||
android:label="DriverLicenseFragment" />
|
android:label="fragment_approver"
|
||||||
<fragment
|
tools:layout="@layout/fragment_approver" />
|
||||||
android:id="@+id/driverProfileFragment"
|
|
||||||
android:name="h_mal.appttude.com.driver.ui.driverprofile.DriverProfileFragment"
|
|
||||||
android:label="fragment_driver_profile"
|
|
||||||
tools:layout="@layout/fragment_driver_profile" />
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/privateHireLicenseFragment"
|
|
||||||
android:name="h_mal.appttude.com.driver.ui.driverprofile.PrivateHireLicenseFragment"
|
|
||||||
android:label="fragment_private_hire_license"
|
|
||||||
tools:layout="@layout/fragment_private_hire_license" />
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/insuranceFragment"
|
|
||||||
android:name="h_mal.appttude.com.driver.ui.vehicleprofile.InsuranceFragment"
|
|
||||||
android:label="InsuranceFragment" />
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/logbookFragment"
|
|
||||||
android:name="h_mal.appttude.com.driver.ui.vehicleprofile.LogbookFragment"
|
|
||||||
android:label="fragment_logbook"
|
|
||||||
tools:layout="@layout/fragment_logbook" />
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/motFragment"
|
|
||||||
android:name="h_mal.appttude.com.driver.ui.vehicleprofile.MotFragment"
|
|
||||||
android:label="MotFragment" />
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/privateHireVehicleFragment"
|
|
||||||
android:name="h_mal.appttude.com.driver.ui.vehicleprofile.PrivateHireVehicleFragment"
|
|
||||||
android:label="fragment_private_hire_vehicle"
|
|
||||||
tools:layout="@layout/fragment_private_hire_vehicle" />
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/vehicleProfileFragment"
|
|
||||||
android:name="h_mal.appttude.com.driver.ui.vehicleprofile.VehicleProfileFragment"
|
|
||||||
android:label="fragment_vehicle_setup"
|
|
||||||
tools:layout="@layout/fragment_vehicle_setup" />
|
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Driver Admin</string>
|
<string name="app_name">Driver Admin</string>
|
||||||
|
<!-- TODO: Remove or change this placeholder text -->
|
||||||
|
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -24,7 +24,11 @@ import androidx.test.espresso.intent.Intents
|
|||||||
import androidx.test.espresso.intent.Intents.intending
|
import androidx.test.espresso.intent.Intents.intending
|
||||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
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.DataHelper
|
||||||
import h_mal.appttude.com.driver.helpers.EspressoHelper.waitForView
|
import h_mal.appttude.com.driver.helpers.EspressoHelper.waitForView
|
||||||
import org.hamcrest.CoreMatchers.allOf
|
import org.hamcrest.CoreMatchers.allOf
|
||||||
@@ -33,6 +37,7 @@ import org.hamcrest.Matcher
|
|||||||
import org.hamcrest.Matchers
|
import org.hamcrest.Matchers
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
open class BaseTestRobot {
|
open class BaseTestRobot {
|
||||||
|
|
||||||
fun fillEditText(resId: Int, text: String?): ViewInteraction =
|
fun fillEditText(resId: Int, text: String?): ViewInteraction =
|
||||||
@@ -51,6 +56,9 @@ open class BaseTestRobot {
|
|||||||
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
|
fun matchText(viewInteraction: ViewInteraction, text: String): ViewInteraction = viewInteraction
|
||||||
.check(matches(withText(text)))
|
.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 matchText(resId: Int, text: String): ViewInteraction = matchText(matchView(resId), text)
|
||||||
|
|
||||||
fun clickListItem(listRes: Int, position: Int) {
|
fun clickListItem(listRes: Int, position: Int) {
|
||||||
@@ -60,7 +68,7 @@ open class BaseTestRobot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction? {
|
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction? {
|
||||||
return onView(withId(recyclerId))
|
return matchView(recyclerId)
|
||||||
.perform(
|
.perform(
|
||||||
// scrollTo will fail the test if no item matches.
|
// scrollTo will fail the test if no item matches.
|
||||||
RecyclerViewActions.scrollTo<VH>(
|
RecyclerViewActions.scrollTo<VH>(
|
||||||
@@ -69,8 +77,38 @@ open class BaseTestRobot {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, resIdForString: Int): ViewInteraction? {
|
||||||
|
return matchView(recyclerId)
|
||||||
|
.perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.scrollTo<VH>(
|
||||||
|
hasDescendant(withText(resIdForString))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> scrollToRecyclerItemByPosition(recyclerId: Int, position: Int): ViewInteraction? {
|
||||||
|
return matchView(recyclerId)
|
||||||
|
.perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.scrollToPosition<VH>(position)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun <VH : ViewHolder> clickViewInRecycler(recyclerId: Int, text: String) {
|
fun <VH : ViewHolder> clickViewInRecycler(recyclerId: Int, text: String) {
|
||||||
scrollToRecyclerItem<VH>(recyclerId, text)?.perform(click())
|
matchView(recyclerId)
|
||||||
|
.perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.actionOnItem<VH>(hasDescendant(withText(text)), click())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <VH : ViewHolder> clickViewInRecycler(recyclerId: Int, resIdForString: Int) {
|
||||||
|
matchView(recyclerId)
|
||||||
|
.perform(
|
||||||
|
// scrollTo will fail the test if no item matches.
|
||||||
|
RecyclerViewActions.actionOnItem<VH>(hasDescendant(withText(resIdForString)), click())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <VH : ViewHolder> clickSubViewInRecycler(recyclerId: Int, text: String, subView: Int) {
|
fun <VH : ViewHolder> clickSubViewInRecycler(recyclerId: Int, text: String, subView: Int) {
|
||||||
|
|||||||
@@ -1,17 +1,32 @@
|
|||||||
package h_mal.appttude.com.driver
|
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.View
|
||||||
|
import android.view.WindowManager
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.test.core.app.ActivityScenario
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.IdlingRegistry
|
import androidx.test.espresso.IdlingRegistry
|
||||||
import androidx.test.espresso.IdlingResource
|
import androidx.test.espresso.IdlingResource
|
||||||
|
import androidx.test.espresso.Root
|
||||||
import androidx.test.espresso.UiController
|
import androidx.test.espresso.UiController
|
||||||
import androidx.test.espresso.ViewAction
|
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.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.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.Matcher
|
||||||
|
import org.hamcrest.TypeSafeMatcher
|
||||||
|
import org.hamcrest.core.AllOf
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
|
||||||
@@ -23,6 +38,8 @@ open class BaseUiTest<T : BaseActivity<*, *>>(
|
|||||||
private lateinit var mActivityScenarioRule: ActivityScenario<T>
|
private lateinit var mActivityScenarioRule: ActivityScenario<T>
|
||||||
private var mIdlingResource: IdlingResource? = null
|
private var mIdlingResource: IdlingResource? = null
|
||||||
|
|
||||||
|
private lateinit var currentActivity: Activity
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
beforeLaunch()
|
beforeLaunch()
|
||||||
@@ -30,7 +47,7 @@ open class BaseUiTest<T : BaseActivity<*, *>>(
|
|||||||
mActivityScenarioRule.onActivity {
|
mActivityScenarioRule.onActivity {
|
||||||
mIdlingResource = it.getIdlingResource()!!
|
mIdlingResource = it.getIdlingResource()!!
|
||||||
IdlingRegistry.getInstance().register(mIdlingResource)
|
IdlingRegistry.getInstance().register(mIdlingResource)
|
||||||
afterLaunch()
|
afterLaunch(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +59,7 @@ open class BaseUiTest<T : BaseActivity<*, *>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getResourceString(@StringRes stringRes: Int): String {
|
fun getResourceString(@StringRes stringRes: Int): String {
|
||||||
return InstrumentationRegistry.getInstrumentation().targetContext.resources.getString(
|
return getInstrumentation().targetContext.resources.getString(
|
||||||
stringRes
|
stringRes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -50,7 +67,7 @@ open class BaseUiTest<T : BaseActivity<*, *>>(
|
|||||||
fun waitFor(delay: Long) {
|
fun waitFor(delay: Long) {
|
||||||
onView(isRoot()).perform(object : ViewAction {
|
onView(isRoot()).perform(object : ViewAction {
|
||||||
override fun getConstraints(): Matcher<View> = isRoot()
|
override fun getConstraints(): Matcher<View> = isRoot()
|
||||||
override fun getDescription(): String? = "wait for $delay milliseconds"
|
override fun getDescription(): String = "wait for $delay milliseconds"
|
||||||
override fun perform(uiController: UiController, v: View?) {
|
override fun perform(uiController: UiController, v: View?) {
|
||||||
uiController.loopMainThreadForAtLeast(delay)
|
uiController.loopMainThreadForAtLeast(delay)
|
||||||
}
|
}
|
||||||
@@ -58,5 +75,55 @@ open class BaseUiTest<T : BaseActivity<*, *>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun beforeLaunch() {}
|
open fun beforeLaunch() {}
|
||||||
open fun afterLaunch() {}
|
open fun afterLaunch(context: Context) {}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
fun checkToastMessage(message: String) {
|
||||||
|
onView(withText(message)).inRoot(object : TypeSafeMatcher<Root>() {
|
||||||
|
override fun describeTo(description: Description?) {
|
||||||
|
description?.appendText("is toast")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun matchesSafely(root: Root): Boolean {
|
||||||
|
root.run {
|
||||||
|
if (windowLayoutParams.get().type == WindowManager.LayoutParams.TYPE_TOAST) {
|
||||||
|
decorView.run {
|
||||||
|
if (windowToken === applicationWindowToken) {
|
||||||
|
// windowToken == appToken means this window isn't contained by any other windows.
|
||||||
|
// if it was a window for an activity, it would have TYPE_BASE_APPLICATION.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).check(matches(isDisplayed()))
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
|
waitFor(3500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkSnackBarDisplayedByMessage(message: String) {
|
||||||
|
onView(
|
||||||
|
CoreMatchers.allOf(
|
||||||
|
withId(com.google.android.material.R.id.snackbar_text),
|
||||||
|
withText(message)
|
||||||
|
)
|
||||||
|
).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCurrentActivity(): Activity {
|
||||||
|
onView(AllOf.allOf(withId(R.id.content), isDisplayed()))
|
||||||
|
.perform(object : BaseViewAction() {
|
||||||
|
override fun setPerform(uiController: UiController?, view: View?) {
|
||||||
|
if (view?.context is Activity) {
|
||||||
|
currentActivity = view.context as Activity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return currentActivity
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,8 @@ import com.google.firebase.database.FirebaseDatabase
|
|||||||
import com.google.firebase.storage.FirebaseStorage
|
import com.google.firebase.storage.FirebaseStorage
|
||||||
import h_mal.appttude.com.driver.base.BaseActivity
|
import h_mal.appttude.com.driver.base.BaseActivity
|
||||||
import h_mal.appttude.com.driver.data.FirebaseAuthSource
|
import h_mal.appttude.com.driver.data.FirebaseAuthSource
|
||||||
|
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
|
||||||
|
import h_mal.appttude.com.driver.data.FirebaseStorageSource
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.tasks.await
|
import kotlinx.coroutines.tasks.await
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@@ -16,7 +18,11 @@ open class FirebaseTest<T : BaseActivity<*, *>>(
|
|||||||
private val signedIn: Boolean = false,
|
private val signedIn: Boolean = false,
|
||||||
private val signOutAfterTest: Boolean = true
|
private val signOutAfterTest: Boolean = true
|
||||||
) : BaseUiTest<T>(activity) {
|
) : BaseUiTest<T>(activity) {
|
||||||
|
|
||||||
private val firebaseAuthSource by lazy { FirebaseAuthSource() }
|
private val firebaseAuthSource by lazy { FirebaseAuthSource() }
|
||||||
|
private val firebaseDatabaseSource by lazy { FirebaseDatabaseSource() }
|
||||||
|
private val firebaseStorageSource by lazy { FirebaseStorageSource() }
|
||||||
|
|
||||||
private var email: String? = null
|
private var email: String? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -58,6 +64,14 @@ open class FirebaseTest<T : BaseActivity<*, *>>(
|
|||||||
firebaseAuthSource.registerUser(signInEmail, password).await().user
|
firebaseAuthSource.registerUser(signInEmail, password).await().user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun login(
|
||||||
|
signInEmail: String,
|
||||||
|
password: String
|
||||||
|
) {
|
||||||
|
email = signInEmail
|
||||||
|
firebaseAuthSource.signIn(signInEmail, password).await()
|
||||||
|
}
|
||||||
|
|
||||||
// remove the user we created for testing
|
// remove the user we created for testing
|
||||||
suspend fun removeUser() {
|
suspend fun removeUser() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package h_mal.appttude.com.driver.helpers
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.test.espresso.UiController
|
||||||
|
import androidx.test.espresso.ViewAction
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
|
||||||
|
import org.hamcrest.Matcher
|
||||||
|
|
||||||
|
open class BaseViewAction: ViewAction {
|
||||||
|
override fun getDescription(): String? = setDescription()
|
||||||
|
|
||||||
|
override fun getConstraints(): Matcher<View> = setConstraints()
|
||||||
|
|
||||||
|
override fun perform(uiController: UiController?, view: View?) {
|
||||||
|
setPerform(uiController, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun setDescription(): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun setConstraints(): Matcher<View> {
|
||||||
|
return isAssignableFrom(View::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun setPerform(uiController: UiController?, view: View?) { }
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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()))
|
||||||
|
}
|
||||||
@@ -8,10 +8,15 @@ import androidx.test.espresso.matcher.ViewMatchers.withTagKey
|
|||||||
import h_mal.appttude.com.driver.BaseTestRobot
|
import h_mal.appttude.com.driver.BaseTestRobot
|
||||||
import h_mal.appttude.com.driver.R
|
import h_mal.appttude.com.driver.R
|
||||||
import h_mal.appttude.com.driver.base.CustomViewHolder
|
import h_mal.appttude.com.driver.base.CustomViewHolder
|
||||||
|
import h_mal.appttude.com.driver.model.DatabaseStatus
|
||||||
|
|
||||||
fun homeAdmin(func: HomeAdminRobot.() -> Unit) = HomeAdminRobot().apply { func() }
|
fun homeAdmin(func: HomeAdminRobot.() -> Unit) = HomeAdminRobot().apply { func() }
|
||||||
class HomeAdminRobot : BaseTestRobot() {
|
class HomeAdminRobot : BaseTestRobot() {
|
||||||
|
|
||||||
|
fun waitUntilDisplayed() {
|
||||||
|
matchViewWaitFor(R.id.recycler_view)
|
||||||
|
}
|
||||||
|
|
||||||
fun openDrawer() {
|
fun openDrawer() {
|
||||||
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
|
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
|
||||||
}
|
}
|
||||||
@@ -39,4 +44,10 @@ class HomeAdminRobot : BaseTestRobot() {
|
|||||||
// Click OK
|
// Click OK
|
||||||
onView(withId(android.R.id.button1)).perform(ViewActions.click())
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package h_mal.appttude.com.driver.tests
|
||||||
|
|
||||||
|
import h_mal.appttude.com.driver.ADMIN_EMAIL
|
||||||
|
import h_mal.appttude.com.driver.FirebaseTest
|
||||||
|
import h_mal.appttude.com.driver.PASSWORD
|
||||||
|
import h_mal.appttude.com.driver.ui.MainActivity
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
open class AdminBaseTest: FirebaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
override fun beforeLaunch() {
|
||||||
|
runBlocking {
|
||||||
|
login(ADMIN_EMAIL, PASSWORD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package h_mal.appttude.com.driver.tests
|
package h_mal.appttude.com.driver.tests
|
||||||
|
|
||||||
import h_mal.appttude.com.driver.ADMIN_EMAIL
|
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.FirebaseTest
|
||||||
import h_mal.appttude.com.driver.robots.homeAdmin
|
import h_mal.appttude.com.driver.robots.homeAdmin
|
||||||
import h_mal.appttude.com.driver.robots.login
|
import h_mal.appttude.com.driver.robots.login
|
||||||
@@ -18,7 +19,17 @@ class UserListTest : FirebaseTest<LoginActivity>(LoginActivity::class.java) {
|
|||||||
homeAdmin {
|
homeAdmin {
|
||||||
clickOnDriverIdentifier("rsaif660@gmail.com")
|
clickOnDriverIdentifier("rsaif660@gmail.com")
|
||||||
submitDialog("ID45")
|
submitDialog("ID45")
|
||||||
waitFor(5000)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loginAsUser_unableToSeeDrivers_loggedIn() {
|
||||||
|
login {
|
||||||
|
waitFor(1100)
|
||||||
|
attemptLogin(DRIVER_EMAIL)
|
||||||
|
}
|
||||||
|
homeAdmin {
|
||||||
|
showNoPermissionsDisplay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import h_mal.appttude.com.driver.viewmodels.*
|
|||||||
class ApplicationViewModelFactory(
|
class ApplicationViewModelFactory(
|
||||||
private val auth: FirebaseAuthSource,
|
private val auth: FirebaseAuthSource,
|
||||||
private val database: FirebaseDatabaseSource,
|
private val database: FirebaseDatabaseSource,
|
||||||
private val storage: FirebaseStorageSource,
|
private val storage: FirebaseStorageSource
|
||||||
private val preferences: PreferenceProvider
|
|
||||||
) : ViewModelProvider.Factory {
|
) : ViewModelProvider.Factory {
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,24 +4,28 @@ import android.app.Application
|
|||||||
import h_mal.appttude.com.driver.data.FirebaseAuthSource
|
import h_mal.appttude.com.driver.data.FirebaseAuthSource
|
||||||
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
|
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
|
||||||
import h_mal.appttude.com.driver.data.FirebaseStorageSource
|
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.Kodein
|
||||||
import org.kodein.di.KodeinAware
|
import org.kodein.di.KodeinAware
|
||||||
import org.kodein.di.android.x.androidXModule
|
import org.kodein.di.android.x.androidXModule
|
||||||
import org.kodein.di.generic.bind
|
import org.kodein.di.generic.bind
|
||||||
import org.kodein.di.generic.instance
|
|
||||||
import org.kodein.di.generic.provider
|
|
||||||
import org.kodein.di.generic.singleton
|
import org.kodein.di.generic.singleton
|
||||||
|
|
||||||
class DriverApplication : Application(), KodeinAware {
|
open class BaseApplication : Application(), KodeinAware {
|
||||||
|
|
||||||
// Kodein aware to initialise the classes used for DI
|
// Kodein aware to initialise the classes used for DI
|
||||||
override val kodein = Kodein.lazy {
|
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 { FirebaseAuthSource() }
|
||||||
bind() from singleton { FirebaseDatabaseSource() }
|
bind() from singleton { FirebaseDatabaseSource() }
|
||||||
bind() from singleton { FirebaseStorageSource() }
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,36 @@
|
|||||||
package h_mal.appttude.com.driver.base
|
package h_mal.appttude.com.driver.base
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.View.OnAttachStateChangeListener
|
||||||
import android.view.ViewGroup.LayoutParams
|
import android.view.ViewGroup.LayoutParams
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.ViewGroup.inflate
|
import android.view.ViewGroup.inflate
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.ViewModelLazy
|
import androidx.lifecycle.ViewModelLazy
|
||||||
import androidx.test.espresso.IdlingResource
|
import androidx.test.espresso.IdlingResource
|
||||||
import androidx.viewbinding.ViewBinding
|
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.R
|
||||||
import h_mal.appttude.com.driver.application.ApplicationViewModelFactory
|
import h_mal.appttude.com.driver.application.ApplicationViewModelFactory
|
||||||
import h_mal.appttude.com.driver.data.ViewState
|
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.getGenericClassAt
|
||||||
import h_mal.appttude.com.driver.utils.GenericsHelper.inflateBindingByType
|
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.KodeinAware
|
||||||
import org.kodein.di.android.kodein
|
import org.kodein.di.android.kodein
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
|
|
||||||
abstract class BaseActivity<V : BaseViewModel, VB : ViewBinding> : AppCompatActivity(), KodeinAware {
|
abstract class BaseActivity<V : BaseViewModel, VB : ViewBinding> : AppCompatActivity(),
|
||||||
|
KodeinAware {
|
||||||
// The Idling Resource which will be null in production.
|
// The Idling Resource which will be null in production.
|
||||||
private var mIdlingResource: BasicIdlingResource? = null
|
private var mIdlingResource: BasicIdlingResource? = null
|
||||||
private lateinit var loadingView: View
|
private lateinit var loadingView: View
|
||||||
@@ -108,7 +117,7 @@ abstract class BaseActivity<V : BaseViewModel, VB : ViewBinding> : AppCompatActi
|
|||||||
* Called in case of failure or some error emitted from the liveData in viewModel
|
* Called in case of failure or some error emitted from the liveData in viewModel
|
||||||
*/
|
*/
|
||||||
open fun onFailure(error: String?) {
|
open fun onFailure(error: String?) {
|
||||||
error?.let { displayToast(it) }
|
error?.let { showToast(it) }
|
||||||
loadingView.fadeOut()
|
loadingView.fadeOut()
|
||||||
mIdlingResource?.setIdleState(true)
|
mIdlingResource?.setIdleState(true)
|
||||||
}
|
}
|
||||||
@@ -139,6 +148,44 @@ abstract class BaseActivity<V : BaseViewModel, VB : ViewBinding> : AppCompatActi
|
|||||||
super.onBackPressed()
|
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<Snackbar>() {
|
||||||
|
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].
|
* Only called from test, creates and returns a new [BasicIdlingResource].
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,15 +8,20 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
|||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.firebase.ui.database.FirebaseRecyclerAdapter
|
import com.firebase.ui.database.FirebaseRecyclerAdapter
|
||||||
import com.firebase.ui.database.FirebaseRecyclerOptions
|
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.getGenericClassAt
|
||||||
import h_mal.appttude.com.driver.utils.GenericsHelper.inflateBindingByType
|
import h_mal.appttude.com.driver.utils.GenericsHelper.inflateBindingByType
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
|
||||||
open class BaseFirebaseAdapter<T: Any, VB : ViewBinding>(options: FirebaseRecyclerOptions<T>, private val layoutInflater: LayoutInflater):
|
open class BaseFirebaseAdapter<T : Any, VB : ViewBinding>(
|
||||||
|
options: FirebaseRecyclerOptions<T>,
|
||||||
|
private val layoutInflater: LayoutInflater
|
||||||
|
) :
|
||||||
FirebaseRecyclerAdapter<T, CustomViewHolder<VB>>(options) {
|
FirebaseRecyclerAdapter<T, CustomViewHolder<VB>>(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
|
private var _binding: VB? = null
|
||||||
val binding: VB
|
val binding: VB
|
||||||
@@ -32,7 +37,7 @@ open class BaseFirebaseAdapter<T: Any, VB : ViewBinding>(options: FirebaseRecycl
|
|||||||
return CustomViewHolder(requireNotNull(_binding))
|
return CustomViewHolder(requireNotNull(_binding))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: CustomViewHolder<VB>, position: Int, model: T) { }
|
override fun onBindViewHolder(holder: CustomViewHolder<VB>, position: Int, model: T) {}
|
||||||
|
|
||||||
override fun getItemId(position: Int): Long {
|
override fun getItemId(position: Int): Long {
|
||||||
return snapshots.getSnapshot(position).key?.toByteArray()
|
return snapshots.getSnapshot(position).key?.toByteArray()
|
||||||
@@ -50,6 +55,26 @@ open class BaseFirebaseAdapter<T: Any, VB : ViewBinding>(options: FirebaseRecycl
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun 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<VB : ViewBinding>(val viewBinding: VB): ViewHolder(viewBinding.root)
|
class CustomViewHolder<VB : ViewBinding>(val viewBinding: VB) : ViewHolder(viewBinding.root)
|
||||||
@@ -20,7 +20,6 @@ import h_mal.appttude.com.driver.utils.PermissionsUtils
|
|||||||
import org.kodein.di.KodeinAware
|
import org.kodein.di.KodeinAware
|
||||||
import org.kodein.di.android.x.kodein
|
import org.kodein.di.android.x.kodein
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
abstract class BaseFragment<V : BaseViewModel, VB : ViewBinding> : Fragment(), KodeinAware {
|
abstract class BaseFragment<V : BaseViewModel, VB : ViewBinding> : Fragment(), KodeinAware {
|
||||||
|
|
||||||
@@ -43,7 +42,6 @@ abstract class BaseFragment<V : BaseViewModel, VB : ViewBinding> : Fragment(), K
|
|||||||
multipleImage = true
|
multipleImage = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
@@ -169,4 +167,7 @@ abstract class BaseFragment<V : BaseViewModel, VB : ViewBinding> : Fragment(), K
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showToast(message: String) = (activity as BaseActivity<*, *>).showToast(message)
|
||||||
|
fun showSnackBar(message: String) = (activity as BaseActivity<*, *>).showSnackBar(message)
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,9 @@ abstract class BaseViewModel : ViewModel() {
|
|||||||
uiState.postValue(ViewState.HasError(Event(error)))
|
uiState.postValue(ViewState.HasError(Event(error)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All in one function for trying an operation and handling its start and failure
|
||||||
|
*/
|
||||||
suspend fun doTryOperation(
|
suspend fun doTryOperation(
|
||||||
defaultErrorMessage: String?,
|
defaultErrorMessage: String?,
|
||||||
operation: suspend () -> Unit
|
operation: suspend () -> Unit
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ package h_mal.appttude.com.driver.data
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.google.android.gms.tasks.Task
|
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
|
import java.io.IOException
|
||||||
|
|
||||||
class FirebaseAuthSource : FirebaseAuthentication {
|
class FirebaseAuthSource : FirebaseAuthentication {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class FirebaseDatabaseSource {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDatabaseReferenceFromLink(link: String) = database.getReferenceFromUrl(link)
|
fun getDatabaseRefFromPath(path: String) = database.getReference(path)
|
||||||
|
|
||||||
val users = database.getReference(USER_CONST)
|
val users = database.getReference(USER_CONST)
|
||||||
|
|
||||||
@@ -46,6 +46,7 @@ class FirebaseDatabaseSource {
|
|||||||
fun getVehicleRef(uid: String) = getUserRef(uid).child(VEHICLE_PROFILE)
|
fun getVehicleRef(uid: String) = getUserRef(uid).child(VEHICLE_PROFILE)
|
||||||
fun getDriverRef(uid: String) = getUserRef(uid).child(DRIVER_PROFILE)
|
fun getDriverRef(uid: String) = getUserRef(uid).child(DRIVER_PROFILE)
|
||||||
fun getApprovalsRef(uid: String) = getUserRef(uid).child(APPROVALS)
|
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 getArchiveRef(uid: String) = getUserRef(uid).child(ARCHIVE)
|
||||||
fun getUserRoleRef(uid: String) = getUserRef(uid).child(PROFILE_ROLE)
|
fun getUserRoleRef(uid: String) = getUserRef(uid).child(PROFILE_ROLE)
|
||||||
fun getDriverNumberRef(uid: String) = getUserRef(uid).child(DRIVER_NUMBER)
|
fun getDriverNumberRef(uid: String) = getUserRef(uid).child(DRIVER_NUMBER)
|
||||||
|
|||||||
@@ -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.base.BaseActivity
|
||||||
import h_mal.appttude.com.driver.data.FirebaseCompletion
|
import h_mal.appttude.com.driver.data.FirebaseCompletion
|
||||||
import h_mal.appttude.com.driver.databinding.UpdateActivityBinding
|
import h_mal.appttude.com.driver.databinding.UpdateActivityBinding
|
||||||
import h_mal.appttude.com.driver.utils.displayToast
|
|
||||||
import h_mal.appttude.com.driver.viewmodels.UpdateUserViewModel
|
import h_mal.appttude.com.driver.viewmodels.UpdateUserViewModel
|
||||||
|
|
||||||
class UpdateActivity : BaseActivity<UpdateUserViewModel, UpdateActivityBinding>() {
|
class UpdateActivity : BaseActivity<UpdateUserViewModel, UpdateActivityBinding>() {
|
||||||
@@ -11,7 +10,7 @@ class UpdateActivity : BaseActivity<UpdateUserViewModel, UpdateActivityBinding>(
|
|||||||
override fun onSuccess(data: Any?) {
|
override fun onSuccess(data: Any?) {
|
||||||
super.onSuccess(data)
|
super.onSuccess(data)
|
||||||
when (data) {
|
when (data) {
|
||||||
is FirebaseCompletion.Changed -> displayToast(data.message)
|
is FirebaseCompletion.Changed -> showToast(data.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -30,7 +30,6 @@ suspend fun DatabaseReference.singleValueEvent(): EventResponse = suspendCorouti
|
|||||||
/**
|
/**
|
||||||
* Read database reference once {@link #DatabaseReference.addListenerForSingleValueEvent}
|
* Read database reference once {@link #DatabaseReference.addListenerForSingleValueEvent}
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @return T
|
* @return T
|
||||||
*/
|
*/
|
||||||
suspend inline fun <reified T : Any> DatabaseReference.getDataFromDatabaseRef(): T? {
|
suspend inline fun <reified T : Any> DatabaseReference.getDataFromDatabaseRef(): T? {
|
||||||
@@ -39,7 +38,23 @@ suspend inline fun <reified T : Any> DatabaseReference.getDataFromDatabaseRef():
|
|||||||
response.snapshot.getValue(T::class.java)
|
response.snapshot.getValue(T::class.java)
|
||||||
}
|
}
|
||||||
is EventResponse.Cancelled -> {
|
is EventResponse.Cancelled -> {
|
||||||
throw response.error.toException()
|
throw FirebaseException(response.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read database reference once {@link #DatabaseReference.addListenerForSingleValueEvent}
|
||||||
|
*
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
suspend inline fun <reified T : Any> DatabaseReference.getListDataFromDatabaseRef(): List<T?> {
|
||||||
|
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 <T: Any> DatabaseReference.getDataFromDatabaseRef(clazz : Class<T>):
|
|||||||
response.snapshot.getValue(clazz)
|
response.snapshot.getValue(clazz)
|
||||||
}
|
}
|
||||||
is EventResponse.Cancelled -> {
|
is EventResponse.Cancelled -> {
|
||||||
throw response.error.toException()
|
throw FirebaseException(response.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ package h_mal.appttude.com.driver.utils
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
@@ -16,7 +15,6 @@ import android.view.inputmethod.InputMethodManager
|
|||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -33,14 +31,6 @@ fun View.hide() {
|
|||||||
this.visibility = View.GONE
|
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) {
|
fun EditText.setEnterPressedListener(action: () -> Unit) {
|
||||||
setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
|
setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
|
||||||
if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
|
if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
|
||||||
|
|||||||
BIN
app/src/main/res/drawable-hdpi/splash_screen.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
app/src/main/res/drawable-mdpi/splash_screen.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/src/main/res/drawable-xhdpi/splash_screen.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
app/src/main/res/drawable-xxhdpi/splash_screen.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/splash_screen.png
Normal file
|
After Width: | Height: | Size: 185 KiB |
5
app/src/main/res/drawable/baseline_check_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/baseline_clear_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/baseline_inbox_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="72dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="72dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,3L4.99,3c-1.11,0 -1.98,0.89 -1.98,2L3,19c0,1.1 0.88,2 1.99,2L19,21c1.1,0 2,-0.9 2,-2L21,5c0,-1.11 -0.9,-2 -2,-2zM19,15h-4c0,1.66 -1.35,3 -3,3s-3,-1.34 -3,-3L4.99,15L4.99,5L19,5v10z"/>
|
||||||
|
</vector>
|
||||||
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 330 KiB |
@@ -102,4 +102,18 @@
|
|||||||
<string name="not_submitted">Not submitted</string>
|
<string name="not_submitted">Not submitted</string>
|
||||||
<string name="empty_view_message">You have no drivers</string>
|
<string name="empty_view_message">You have no drivers</string>
|
||||||
<string name="approval_status">approval status</string>
|
<string name="approval_status">approval status</string>
|
||||||
|
<string name="approve">Approve</string>
|
||||||
|
<string name="deny">Deny</string>
|
||||||
|
<string name="decline">Decline</string>
|
||||||
|
<string name="no_drivers_to_show">No drivers to show</string>
|
||||||
|
<string name="no_drivers_subtext">There are no drivers present for your organisation.</string>
|
||||||
|
<string name="no_permission">You do not have permissions to view</string>
|
||||||
|
<string name="no_permission_subtext">You are not a super user. Contact us to get super user access.</string>
|
||||||
|
<string name="cannot_retrieve">Cannot retrieve data.</string>
|
||||||
|
<string name="cannot_retrieve_subtext">Check you are logged in correctly and have a working connection.</string>
|
||||||
|
<string name="no_connection">No connection</string>
|
||||||
|
<string name="no_connection_subtext">Make you have a valid internet connection.</string>
|
||||||
|
<string name="no_authorization">Authentication has failed</string>
|
||||||
|
<string name="no_authorization_subtext">There is a problem with authentication.</string>
|
||||||
|
<string name="image_icon_for_feedback_view">Image icon for feedback view.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
{
|
{
|
||||||
"rules": {
|
"rules": {
|
||||||
"user": {
|
"user": {
|
||||||
".read": true,
|
".read": "root.child('user').child(auth.uid).child('role').val() == 'admin'",
|
||||||
"$user_id": {
|
"$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'"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||