- retrieve thumbnail or no image

- changes to storage ref in process retrieval in process

 ** broken commit **
This commit is contained in:
2023-10-19 21:39:59 +01:00
parent 401b43f3c3
commit 32def043f9
56 changed files with 896 additions and 314 deletions

View File

@@ -1,5 +1,5 @@
{
"licenseExpiry": "27/04/2019",
"licenseExpiry": "27/04/2032",
"licenseImageString": "driver_license_driver.jpg",
"licenseNumber": "FARME100165AB5EW"
}

View File

@@ -16,12 +16,14 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.internal.util.Checks
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.rule.GrantPermissionRule
import com.google.gson.Gson
import h_mal.appttude.com.driver.base.BaseActivity
import h_mal.appttude.com.driver.helpers.BaseViewAction
import h_mal.appttude.com.driver.helpers.SnapshotRule
import kotlinx.coroutines.runBlocking
import org.hamcrest.CoreMatchers
import org.hamcrest.Matcher
import org.hamcrest.core.AllOf
@@ -60,9 +62,11 @@ open class BaseUiTest<T : BaseActivity<*, *>>(
beforeLaunch()
mActivityScenarioRule = ActivityScenario.launch(activity)
mActivityScenarioRule.onActivity {
runBlocking {
mIdlingResource = it.getIdlingResource()!!
IdlingRegistry.getInstance().register(mIdlingResource)
}
}
afterLaunch()
}

View File

@@ -35,12 +35,15 @@ open class FirebaseTest<T : BaseActivity<*, *>>(
val localHost = "10.0.2.2"
FirebaseAuth.getInstance().useEmulator(localHost, 9099)
FirebaseDatabase.getInstance().useEmulator(localHost, 9000)
FirebaseDatabase.getInstance().useEmulator(localHost, 9001)
FirebaseStorage.getInstance().useEmulator(localHost, 9199)
}
}
override fun beforeLaunch() {
// // sign out if it failed to sign out
// if (firebaseAuthSource.getUser() != null) firebaseAuthSource.logOut()
if (registered) {
runBlocking {
setupUser()

View File

@@ -7,7 +7,7 @@ import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.withId
import h_mal.appttude.com.driver.helpers.EspressoHelper.trying
import h_mal.appttude.com.driver.model.Model
import h_mal.appttude.com.driver.base.Model
import org.hamcrest.CoreMatchers.allOf
import java.time.LocalDate
import java.time.format.DateTimeFormatter

View File

@@ -21,7 +21,7 @@ class HomeRobot : BaseTestRobot() {
fun updateProfile() {
openDrawer()
clickButton(R.id.nav_user_settings)
clickButton(R.id.nav_update_profile)
}
fun openDriverProfile() = clickButton(R.id.driver)

View File

@@ -9,7 +9,7 @@ import h_mal.appttude.com.driver.model.DriverProfile
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.Model
import h_mal.appttude.com.driver.base.Model
import h_mal.appttude.com.driver.model.Mot
import h_mal.appttude.com.driver.model.PrivateHireLicense
import h_mal.appttude.com.driver.model.PrivateHireVehicle

View File

@@ -7,6 +7,7 @@ open class DriverProfileTest : DataSubmissionTest() {
override fun afterLaunch() {
super.afterLaunch()
home {
waitFor(1500)
openDriverProfile()
}
}

View File

@@ -26,6 +26,8 @@ class SubmitNewDriverDataTest : DriverProfileTest() {
driversLicense {
val data = getAssetData<DriversLicense>()
submitAndValidate(data)
waitFor(17000)
}
}

View File

@@ -45,8 +45,7 @@ class ApplicationViewModelFactory(
)
isAssignableFrom(VehicleProfileViewModel::class.java) -> VehicleProfileViewModel(
auth,
database,
storage
database
)
isAssignableFrom(InsuranceViewModel::class.java) -> InsuranceViewModel(
auth,

View File

@@ -2,7 +2,7 @@ package h_mal.appttude.com.driver.ui.driverprofile
import android.net.Uri
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseFragment
import h_mal.appttude.com.driver.base.ImageFormSubmissionFragment
import h_mal.appttude.com.driver.databinding.FragmentDriverLicenseBinding
import h_mal.appttude.com.driver.dialogs.DateDialog
import h_mal.appttude.com.driver.model.DriversLicense
@@ -11,7 +11,7 @@ import h_mal.appttude.com.driver.utils.setGlideImage
import h_mal.appttude.com.driver.viewmodels.DriverLicenseViewModel
class DriverLicenseFragment :
DataSubmissionBaseFragment<DriverLicenseViewModel, FragmentDriverLicenseBinding, DriversLicense>() {
ImageFormSubmissionFragment<DriverLicenseViewModel, FragmentDriverLicenseBinding, DriversLicense>() {
override fun setupView(binding: FragmentDriverLicenseBinding) {
binding.apply {
@@ -25,32 +25,31 @@ class DriverLicenseFragment :
}
licNo.setTextOnChange { model.licenseNumber = it }
searchImage.setOnClickListener { openGalleryWithPermissionRequest() }
searchImage.setOnClickListener { openGalleryForImageSelection() }
submit.setOnClickListener {
validateEditTexts(licExpiry, licNo).isTrue {
viewModel.setDataInDatabase(model, picUri)
}
validateEditTexts(licExpiry, licNo).isTrue { submitDocument() }
}
}
}
override fun setFields(data: DriversLicense) {
super.setFields(data)
applyBinding {
licNo.setText(data.licenseNumber)
licExpiry.setText(data.licenseExpiry)
data.licenseImageString?.setImages{
driversliImg.setGlideImage(it.second)
}
}
}
override fun onImageGalleryResult(imageUri: Uri?) {
override fun setImage(image: StorageReference?, thumbnail: StorageReference?) {
thumbnail?.let {
binding.driversliImg.setGlideImage(it)
return
}
binding.driversliImg.setGlideImage(image)
}
override fun onImageGalleryResult(imageUri: Uri) {
super.onImageGalleryResult(imageUri)
applyBinding {
driversliImg.setGlideImage(imageUri)
}
binding.driversliImg.setGlideImage(imageUri)
}
}

View File

@@ -2,7 +2,7 @@ package h_mal.appttude.com.driver.ui.driverprofile
import android.net.Uri
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseFragment
import h_mal.appttude.com.driver.base.ImageFormSubmissionFragment
import h_mal.appttude.com.driver.databinding.FragmentDriverProfileBinding
import h_mal.appttude.com.driver.dialogs.DateDialog
import h_mal.appttude.com.driver.model.DriverProfile
@@ -12,7 +12,7 @@ import h_mal.appttude.com.driver.viewmodels.DriverProfileViewModel
class DriverProfileFragment :
DataSubmissionBaseFragment<DriverProfileViewModel, FragmentDriverProfileBinding, DriverProfile>() {
ImageFormSubmissionFragment<DriverProfileViewModel, FragmentDriverProfileBinding, DriverProfile>() {
override fun setupView(binding: FragmentDriverProfileBinding) = binding.run {
namesInput.setTextOnChange { model.forenames = it }
@@ -35,7 +35,7 @@ class DriverProfileFragment :
}
}
}
addPhoto.setOnClickListener { openGalleryWithPermissionRequest() }
addPhoto.setOnClickListener { openGalleryForImageSelection() }
submit.setOnClickListener { submit() }
}
@@ -45,13 +45,12 @@ class DriverProfileFragment :
namesInput, addressInput, postcodeInput,
dobInput, niNumber, dateFirst
).isTrue {
viewModel.setDataInDatabase(model, picUri)
submitDocument()
}
}
}
override fun setFields(data: DriverProfile) {
super.setFields(data)
applyBinding {
namesInput.setText(data.forenames)
addressInput.setText(data.address)
@@ -59,18 +58,16 @@ class DriverProfileFragment :
dobInput.setText(data.dob)
niNumber.setText(data.ni)
dateFirst.setText(data.dateFirst)
data.driverPic?.setImages {
driverPic.setGlideImage(it.second)
}
}
}
override fun onImageGalleryResult(imageUri: Uri?) {
override fun setImage(image: StorageReference, thumbnail: StorageReference) {
binding.driverPic.setGlideImage(thumbnail)
}
override fun onImageGalleryResult(imageUri: Uri) {
super.onImageGalleryResult(imageUri)
applyBinding {
driverPic.setGlideImage(imageUri)
}
binding.driverPic.setGlideImage(imageUri)
}
}

View File

@@ -2,7 +2,7 @@ package h_mal.appttude.com.driver.ui.driverprofile
import android.net.Uri
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseFragment
import h_mal.appttude.com.driver.base.ImageFormSubmissionFragment
import h_mal.appttude.com.driver.databinding.FragmentPrivateHireLicenseBinding
import h_mal.appttude.com.driver.dialogs.DateDialog
import h_mal.appttude.com.driver.model.PrivateHireLicense
@@ -11,7 +11,7 @@ import h_mal.appttude.com.driver.utils.setGlideImage
import h_mal.appttude.com.driver.viewmodels.PrivateHireLicenseViewModel
class PrivateHireLicenseFragment : DataSubmissionBaseFragment
class PrivateHireLicenseFragment : ImageFormSubmissionFragment
<PrivateHireLicenseViewModel, FragmentPrivateHireLicenseBinding, PrivateHireLicense>() {
override fun setupView(binding: FragmentPrivateHireLicenseBinding) = binding.run {
@@ -25,14 +25,14 @@ class PrivateHireLicenseFragment : DataSubmissionBaseFragment
}
}
uploadphlic.setOnClickListener { openGalleryWithPermissionRequest() }
uploadphlic.setOnClickListener { openGalleryForImageSelection() }
submit.setOnClickListener { submit() }
}
override fun submit() {
applyBinding {
validateEditTexts(phNo, phExpiry).isTrue {
viewModel.setDataInDatabase(model, picUri)
submitDocument()
}
}
}
@@ -42,15 +42,16 @@ class PrivateHireLicenseFragment : DataSubmissionBaseFragment
applyBinding {
phNo.setText(data.phNumber)
phExpiry.setText(data.phExpiry)
data.phImageString?.setImages { imageView2.setGlideImage(it.second) }
}
}
override fun onImageGalleryResult(imageUri: Uri?) {
super.onImageGalleryResult(imageUri)
applyBinding {
imageView2.setGlideImage(imageUri)
override fun setImage(image: StorageReference, thumbnail: StorageReference) {
binding.imageView2.setGlideImage(thumbnail)
}
override fun onImageGalleryResult(imageUri: Uri) {
super.onImageGalleryResult(imageUri)
binding.imageView2.setGlideImage(imageUri)
}
}

View File

@@ -5,7 +5,8 @@ import android.os.Bundle
import android.view.View
import android.widget.ImageView
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseFragment
import h_mal.appttude.com.driver.base.MultiImageFormSubmissionFragment3
import h_mal.appttude.com.driver.data.ImageCollection
import h_mal.appttude.com.driver.databinding.FragmentInsuranceBinding
import h_mal.appttude.com.driver.dialogs.DateDialog
import h_mal.appttude.com.driver.model.Insurance
@@ -15,13 +16,10 @@ import h_mal.appttude.com.driver.viewmodels.InsuranceViewModel
class InsuranceFragment :
DataSubmissionBaseFragment<InsuranceViewModel, FragmentInsuranceBinding, Insurance>() {
private var selectedImages: List<Uri>? = listOf()
MultiImageFormSubmissionFragment3<InsuranceViewModel, FragmentInsuranceBinding, Insurance>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setImageSelectionAsMultiple()
applyBinding {
insurer.setTextOnChange { model.insurerName = it }
@@ -32,7 +30,7 @@ class InsuranceFragment :
}
}
}
uploadInsurance.setOnClickListener { openGalleryWithPermissionRequest() }
uploadInsurance.setOnClickListener { openGalleryForImageSelection() }
submit.setOnClickListener { submit() }
}
}
@@ -57,35 +55,24 @@ class InsuranceFragment :
super.submit()
applyBinding {
validateEditTexts(insurer, insuranceExp).isTrue {
viewModel.setDataInDatabase(model, selectedImages)
submitDocument()
}
}
}
override fun setFields(data: Insurance) {
super.setFields(data)
applyBinding {
insurer.setText(model.insurerName)
insuranceExp.setText(model.expiryDate)
data.photoStrings?.also {
val keys = viewModel.getMultipleImagesAndThumbnails(it).map {i -> i.key }
updateImageCarousal(keys)
}
}
}
override fun onImageGalleryResult(imageUris: List<Uri>?) {
selectedImages = imageUris
selectedImages?.let { updateImageCarousal(it) }
override fun setImages(collection: ImageCollection) {
updateImageCarousal(collection.collection.map { it.second })
}
override fun onSuccess(data: Any?) {
super.onSuccess(data)
if (data is Map<*,*>) {
updateImageCarousal(data.map { it.value })
}
override fun onImageGalleryResult(imageUris: List<Uri>) {
updateImageCarousal(imageUris)
}
}

View File

@@ -2,7 +2,7 @@ package h_mal.appttude.com.driver.ui.vehicleprofile
import android.net.Uri
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseFragment
import h_mal.appttude.com.driver.base.ImageFormSubmissionFragment
import h_mal.appttude.com.driver.databinding.FragmentLogbookBinding
import h_mal.appttude.com.driver.model.Logbook
import h_mal.appttude.com.driver.utils.isTrue
@@ -11,11 +11,11 @@ import h_mal.appttude.com.driver.viewmodels.LogbookViewModel
class LogbookFragment :
DataSubmissionBaseFragment<LogbookViewModel, FragmentLogbookBinding, Logbook>() {
ImageFormSubmissionFragment<LogbookViewModel, FragmentLogbookBinding, Logbook>() {
override fun setupView(binding: FragmentLogbookBinding) = binding.run {
v5cNo.setTextOnChange { model.v5cnumber = it }
uploadLb.setOnClickListener { openGalleryWithPermissionRequest() }
uploadLb.setOnClickListener { openGalleryForImageSelection() }
submit.setOnClickListener { submit() }
}
@@ -23,24 +23,23 @@ class LogbookFragment :
super.submit()
applyBinding {
validateEditTexts(v5cNo).isTrue {
viewModel.setDataInDatabase(model, picUri)
submitDocument()
}
}
}
override fun setFields(data: Logbook) {
super.setFields(data)
applyBinding {
v5cNo.setText(data.v5cnumber)
data.photoString?.setImages { logBookImg.setGlideImage(it.second) }
}
}
override fun setImage(image: StorageReference, thumbnail: StorageReference) {
binding.logBookImg.setGlideImage(thumbnail)
}
override fun onImageGalleryResult(imageUri: Uri?) {
override fun onImageGalleryResult(imageUri: Uri) {
super.onImageGalleryResult(imageUri)
applyBinding {
logBookImg.setGlideImage(picUri)
}
binding.logBookImg.setGlideImage(imageUri)
}
}

View File

@@ -2,7 +2,7 @@ package h_mal.appttude.com.driver.ui.vehicleprofile
import android.net.Uri
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseFragment
import h_mal.appttude.com.driver.base.ImageFormSubmissionFragment
import h_mal.appttude.com.driver.databinding.FragmentMotBinding
import h_mal.appttude.com.driver.dialogs.DateDialog
import h_mal.appttude.com.driver.model.Mot
@@ -11,7 +11,7 @@ import h_mal.appttude.com.driver.utils.setGlideImage
import h_mal.appttude.com.driver.viewmodels.MotViewModel
class MotFragment : DataSubmissionBaseFragment<MotViewModel, FragmentMotBinding, Mot>() {
class MotFragment : ImageFormSubmissionFragment<MotViewModel, FragmentMotBinding, Mot>() {
override fun setupView(binding: FragmentMotBinding) = binding.run {
motExpiry.apply {
@@ -22,26 +22,26 @@ class MotFragment : DataSubmissionBaseFragment<MotViewModel, FragmentMotBinding,
}
}
uploadmot.setOnClickListener { openGalleryWithPermissionRequest() }
uploadmot.setOnClickListener { openGalleryForImageSelection() }
submit.setOnClickListener {
validateEditTexts(motExpiry).isTrue {
viewModel.setDataInDatabase(model, picUri)
submitDocument()
}
}
}
override fun setFields(data: Mot) {
super.setFields(data)
applyBinding {
motExpiry.setText(data.motExpiry)
data.motImageString?.setImages { motImg.setGlideImage(it.second) }
}
}
override fun onImageGalleryResult(imageUri: Uri?) {
super.onImageGalleryResult(imageUri)
applyBinding {
motImg.setGlideImage(imageUri)
override fun setImage(image: StorageReference, thumbnail: StorageReference) {
binding.motImg.setGlideImage(thumbnail)
}
override fun onImageGalleryResult(imageUri: Uri) {
super.onImageGalleryResult(imageUri)
binding.motImg.setGlideImage(imageUri)
}
}

View File

@@ -2,7 +2,7 @@ package h_mal.appttude.com.driver.ui.vehicleprofile
import android.net.Uri
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseFragment
import h_mal.appttude.com.driver.base.ImageFormSubmissionFragment
import h_mal.appttude.com.driver.databinding.FragmentPrivateHireLicenseBinding
import h_mal.appttude.com.driver.dialogs.DateDialog
import h_mal.appttude.com.driver.model.PrivateHireVehicle
@@ -12,7 +12,7 @@ import h_mal.appttude.com.driver.viewmodels.PrivateHireVehicleViewModel
class PrivateHireVehicleFragment :
DataSubmissionBaseFragment<PrivateHireVehicleViewModel, FragmentPrivateHireLicenseBinding, PrivateHireVehicle>() {
ImageFormSubmissionFragment<PrivateHireVehicleViewModel, FragmentPrivateHireLicenseBinding, PrivateHireVehicle>() {
override fun setupView(binding: FragmentPrivateHireLicenseBinding) = binding.run {
phNo.setTextOnChange { model.phCarNumber = it }
@@ -24,28 +24,29 @@ class PrivateHireVehicleFragment :
}
}
uploadphlic.setOnClickListener { openGalleryWithPermissionRequest() }
uploadphlic.setOnClickListener { openGalleryForImageSelection() }
submit.setOnClickListener {
validateEditTexts(phNo, phExpiry).isTrue {
viewModel.setDataInDatabase(model, picUri)
submitDocument()
}
}
}
override fun setFields(data: PrivateHireVehicle) {
super.setFields(data)
override fun setFields(
data: PrivateHireVehicle
) {
applyBinding {
phNo.setText(data.phCarNumber)
phExpiry.setText(data.phCarExpiry)
data.phCarImageString?.setImages { imageView2.setGlideImage(it.second) }
}
}
override fun setImage(image: StorageReference, thumbnail: StorageReference) {
binding.imageView2.setGlideImage(thumbnail)
}
override fun onImageGalleryResult(imageUri: Uri?) {
override fun onImageGalleryResult(imageUri: Uri) {
super.onImageGalleryResult(imageUri)
applyBinding {
imageView2.setGlideImage(imageUri)
}
binding.imageView2.setGlideImage(imageUri)
}
}

View File

@@ -1,6 +1,6 @@
package h_mal.appttude.com.driver.ui.vehicleprofile
import h_mal.appttude.com.driver.base.DataSubmissionBaseFragment
import h_mal.appttude.com.driver.base.FormSubmissionFragment
import h_mal.appttude.com.driver.databinding.FragmentVehicleSetupBinding
import h_mal.appttude.com.driver.dialogs.DateDialog
import h_mal.appttude.com.driver.model.VehicleProfile
@@ -8,8 +8,7 @@ import h_mal.appttude.com.driver.utils.isTrue
import h_mal.appttude.com.driver.viewmodels.VehicleProfileViewModel
class VehicleProfileFragment : DataSubmissionBaseFragment
<VehicleProfileViewModel, FragmentVehicleSetupBinding, VehicleProfile>() {
class VehicleProfileFragment : FormSubmissionFragment<VehicleProfileViewModel, FragmentVehicleSetupBinding, VehicleProfile>() {
override fun setupView(binding: FragmentVehicleSetupBinding) = binding.run {
reg.setTextOnChange { model.reg = it }
@@ -39,7 +38,7 @@ class VehicleProfileFragment : DataSubmissionBaseFragment
postcode,
startDate
).isTrue {
viewModel.setDataInDatabase(model)
submitDocument()
}
}
}

View File

@@ -1,35 +1,33 @@
package h_mal.appttude.com.driver.viewmodels
import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.data.DRIVERS_LICENSE_SREF
import h_mal.appttude.com.driver.base.DataSubmissionViewModel2
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.Storage
import h_mal.appttude.com.driver.model.DriversLicense
import h_mal.appttude.com.driver.utils.Coroutines.io
import kotlinx.coroutines.Job
import h_mal.appttude.com.driver.utils.DateUtils.parseDateStringIntoCalender
import org.joda.time.LocalDate
class DriverLicenseViewModel(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
storage: FirebaseStorageSource
) : DataSubmissionBaseViewModel<DriversLicense>(auth, database, storage) {
) : DataSubmissionViewModel2<DriversLicense>(auth, database, storage, Storage.DRIVERS_LICENSE) {
override val databaseRef: DatabaseReference = database.getDriverLicenseRef(uid)
override val storageRef: StorageReference = storage.driversLicenseStorageRef(uid)
override val objectName: String = "drivers license"
override fun getDataFromDatabase() = retrieveDataFromDatabase<DriversLicense>()
override fun setDataInDatabase(data: DriversLicense, localImageUri: Uri?) = io {
doTryOperation("Failed to upload $objectName") {
val imageUrl = getImageUrl(localImageUri, data.licenseImageString)
data.licenseImageString = imageUrl
postDataToDatabase(data)
override fun validateData(data: DriversLicense): Boolean {
data.licenseNumber.validateStringOrThrow("License number")
if (parseDateStringIntoCalender(data.licenseExpiry!!).isBefore(LocalDate.now())) {
onError("License expiry cannot be before today")
return false
}
return true
}
}

View File

@@ -1,40 +0,0 @@
package h_mal.appttude.com.driver.viewmodels
import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.base.DataSubmissionViewModel2
import h_mal.appttude.com.driver.data.DRIVERS_LICENSE_SREF
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.Storage
import h_mal.appttude.com.driver.model.DriversLicense
import h_mal.appttude.com.driver.utils.Coroutines.io
import kotlinx.coroutines.Job
class DriverLicenseViewModel2(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
storage: FirebaseStorageSource
) : DataSubmissionViewModel2<DriversLicense>(auth, database, storage) {
override val databaseRef: DatabaseReference = database.getDriverLicenseRef(uid)
override val storageRef: StorageReference = storage.driversLicenseStorageRef(uid)
override fun validateData(data: DriversLicense): Boolean {
// TODO Not yet implemented
return true
}
override fun setDataAfterUpload(data: DriversLicense, filename: String): DriversLicense {
return data.apply {
licenseImageString = filename
}
}
fun postDataToDatabase(imageUri: Uri, data: DriversLicense) {
postDataToDatabase(imageUri, data, Storage.DRIVERS_LICENSE)
}
}

View File

@@ -4,33 +4,39 @@ import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.base.DataSubmissionViewModel2
import h_mal.appttude.com.driver.data.DRIVER_PROFILE
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.PROFILE_SREF
import h_mal.appttude.com.driver.data.Storage
import h_mal.appttude.com.driver.model.DriverProfile
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.DateUtils
import kotlinx.coroutines.Job
import org.joda.time.LocalDate
class DriverProfileViewModel(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
storage: FirebaseStorageSource
) : DataSubmissionBaseViewModel<DriverProfile>(auth, database, storage) {
) : DataSubmissionViewModel2<DriverProfile>(auth, database, storage, Storage.PROFILE) {
override val databaseRef: DatabaseReference = database.getDriverDetailsRef(uid)
override val storageRef: StorageReference = storage.profileImageStorageRef(uid)
override val objectName: String = "drivers profile"
override fun getDataFromDatabase() = retrieveDataFromDatabase<DriverProfile>()
override fun setDataInDatabase(data: DriverProfile, localImageUri: Uri?) = io {
doTryOperation("Failed to upload $objectName") {
val imageUrl = getImageUrl(localImageUri, data.driverPic)
data.driverPic = imageUrl
postDataToDatabase(data)
override fun validateData(data: DriverProfile): Boolean {
data.ni.validateStringOrThrow("National Insurance number")
data.address.validateStringOrThrow("Address")
data.postcode.validateStringOrThrow("Postcode")
data.forenames.validateStringOrThrow("Name")
data.dob
if (DateUtils.parseDateStringIntoCalender(data.dob!!).isAfter(LocalDate.now().minusYears(17))) {
onError("Driver cannot be under 17")
return false
}
return true
}
}

View File

@@ -1,43 +1,32 @@
package h_mal.appttude.com.driver.viewmodels
import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.base.DataSubmissionViewModel3
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.INSURANCE_SREF
import h_mal.appttude.com.driver.data.Storage
import h_mal.appttude.com.driver.model.Insurance
import h_mal.appttude.com.driver.utils.Coroutines.io
import kotlinx.coroutines.Job
import h_mal.appttude.com.driver.utils.DateUtils
import org.joda.time.LocalDate
class InsuranceViewModel(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
storage: FirebaseStorageSource
) : DataSubmissionBaseViewModel<Insurance>(auth, database, storage) {
) : DataSubmissionViewModel3<Insurance>(auth, database, storage, Storage.INSURANCE) {
override val databaseRef: DatabaseReference = database.getInsuranceDetailsRef(uid)
override val storageRef: StorageReference = storage.insuranceStorageRef(uid)
override val objectName: String = "insurance"
override fun getDataFromDatabase() = retrieveDataFromDatabase<Insurance>()
override fun setDataInDatabase(data: Insurance, localImageUris: List<Uri?>?) = io {
doTryOperation("Failed to upload $objectName") {
val imageUrls = if (!localImageUris.isNullOrEmpty()) {
getImageUrls(localImageUris).toMutableList()
} else {
data.photoStrings
override fun validateData(data: Insurance): Boolean {
data.insurerName.validateStringOrThrow("Insurer name")
if (DateUtils.parseDateStringIntoCalender(data.expiryDate!!).isBefore(LocalDate.now())) {
onError("Insurance expiry cannot be before today")
return false
}
if (imageUrls.isNullOrEmpty()) {
onError("no images selected")
return@doTryOperation
return true
}
data.photoStrings = imageUrls
postDataToDatabase(data)
}
}
}

View File

@@ -1,37 +1,26 @@
package h_mal.appttude.com.driver.viewmodels
import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.data.DRIVER_PROFILE
import h_mal.appttude.com.driver.base.DataSubmissionViewModel2
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.LOG_BOOK_SREF
import h_mal.appttude.com.driver.model.DriverProfile
import h_mal.appttude.com.driver.data.Storage
import h_mal.appttude.com.driver.model.Logbook
import h_mal.appttude.com.driver.utils.Coroutines.io
import kotlinx.coroutines.Job
class LogbookViewModel(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
storage: FirebaseStorageSource
) : DataSubmissionBaseViewModel<Logbook>(auth, database, storage) {
) : DataSubmissionViewModel2<Logbook>(auth, database, storage, Storage.LOG_BOOK) {
override val databaseRef: DatabaseReference = database.getLogbookRef(uid)
override val storageRef: StorageReference = storage.logBookStorageRef(uid)
override val objectName: String = "Log book"
override fun getDataFromDatabase() = retrieveDataFromDatabase<Logbook>()
override fun setDataInDatabase(data: Logbook, localImageUri: Uri?) = io {
doTryOperation("Failed to upload $objectName") {
val imageUrl = getImageUrl(localImageUri, data.photoString)
data.photoString = imageUrl
postDataToDatabase(data)
}
override fun validateData(data: Logbook): Boolean {
data.v5cnumber.validateStringOrThrow("V5C number")
return true
}
}

View File

@@ -4,35 +4,36 @@ import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.base.DataSubmissionViewModel2
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.LOG_BOOK_SREF
import h_mal.appttude.com.driver.data.MOT
import h_mal.appttude.com.driver.data.MOT_SREF
import h_mal.appttude.com.driver.data.Storage
import h_mal.appttude.com.driver.model.Logbook
import h_mal.appttude.com.driver.model.Mot
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.DateUtils
import kotlinx.coroutines.Job
import org.joda.time.LocalDate
class MotViewModel(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
storage: FirebaseStorageSource
) : DataSubmissionBaseViewModel<Mot>(auth, database, storage) {
) : DataSubmissionViewModel2<Mot>(auth, database, storage, Storage.MOT) {
override val databaseRef: DatabaseReference = database.getMotDetailsRef(uid)
override val storageRef: StorageReference? = storage.motStorageRef(uid)
override val objectName: String = "vehicle profile"
override val storageRef: StorageReference = storage.motStorageRef(uid)
override fun getDataFromDatabase() = retrieveDataFromDatabase<Mot>()
override fun setDataInDatabase(data: Mot, localImageUri: Uri?) = io {
doTryOperation("Failed to upload $objectName") {
val imageUrl = getImageUrl(localImageUri, data.motImageString)
data.motImageString = imageUrl
postDataToDatabase(data)
override fun validateData(data: Mot): Boolean {
if (DateUtils.parseDateStringIntoCalender(data.motExpiry!!).isBefore(LocalDate.now())) {
onError("MOT expiry cannot be before today")
return false
}
return true
}
}

View File

@@ -4,39 +4,37 @@ import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.base.DataSubmissionViewModel2
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.MOT_SREF
import h_mal.appttude.com.driver.data.PRIVATE_HIRE_SREF
import h_mal.appttude.com.driver.data.Storage
import h_mal.appttude.com.driver.model.Mot
import h_mal.appttude.com.driver.model.PrivateHireLicense
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.DateUtils
import kotlinx.coroutines.Job
import org.joda.time.LocalDate
class PrivateHireLicenseViewModel(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
storage: FirebaseStorageSource
) : DataSubmissionBaseViewModel<PrivateHireLicense>(auth, database, storage) {
) : DataSubmissionViewModel2<PrivateHireLicense>(auth, database, storage, Storage.DRIVERS_LICENSE) {
override val databaseRef: DatabaseReference = database.getPrivateHireRef(uid)
override val storageRef: StorageReference = storage.privateHireStorageRef(uid)
override val objectName: String = "private hire license"
override fun getDataFromDatabase() = retrieveDataFromDatabase<PrivateHireLicense>()
override fun setDataInDatabase(data: PrivateHireLicense, localImageUri: Uri?) = io {
doTryOperation("Failed to upload private hire license") {
val imageUrl = getImageUrl(localImageUri, data.phImageString)
val driverLicense = PrivateHireLicense(
phExpiry = data.phExpiry,
phNumber = data.phNumber,
phImageString = imageUrl
)
postDataToDatabase(driverLicense)
override fun validateData(data: PrivateHireLicense): Boolean {
data.phNumber.validateStringOrThrow("License number")
if (DateUtils.parseDateStringIntoCalender(data.phExpiry!!).isBefore(LocalDate.now())) {
onError("License expiry cannot be before today")
return false
}
return true
}
}

View File

@@ -1,36 +1,37 @@
package h_mal.appttude.com.driver.viewmodels
import android.net.Uri
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.base.DataSubmissionViewModel2
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.PRIVATE_HIRE_SREF
import h_mal.appttude.com.driver.data.PRIVATE_HIRE_VEHICLE_SREF
import h_mal.appttude.com.driver.model.PrivateHireLicense
import h_mal.appttude.com.driver.data.Storage
import h_mal.appttude.com.driver.model.PrivateHireVehicle
import h_mal.appttude.com.driver.utils.Coroutines.io
import kotlinx.coroutines.Job
import h_mal.appttude.com.driver.utils.DateUtils
import org.joda.time.LocalDate
class PrivateHireVehicleViewModel(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
storage: FirebaseStorageSource
) : DataSubmissionBaseViewModel<PrivateHireVehicle>(auth, database, storage) {
) : DataSubmissionViewModel2<PrivateHireVehicle>(
auth,
database,
storage,
Storage.PRIVATE_HIRE_VEHICLE
) {
override val databaseRef: DatabaseReference = database.getPrivateHireVehicleRef(uid)
override val storageRef: StorageReference = storage.privateHireVehicleStorageRef(uid)
override val objectName: String = "private hire vehicle license"
override fun getDataFromDatabase() = retrieveDataFromDatabase<PrivateHireVehicle>()
override fun setDataInDatabase(data: PrivateHireVehicle, localImageUri: Uri?) = io {
doTryOperation("Failed to upload $objectName") {
val imageUrl = getImageUrl(localImageUri, data.phCarImageString)
data.phCarImageString = imageUrl
postDataToDatabase(data)
override fun validateData(data: PrivateHireVehicle): Boolean {
data.phCarNumber.validateStringOrThrow("License number")
if (DateUtils.parseDateStringIntoCalender(data.phCarExpiry!!).isBefore(LocalDate.now())) {
onError("License expiry cannot be before today")
return false
}
return true
}
}

View File

@@ -3,6 +3,7 @@ package h_mal.appttude.com.driver.viewmodels
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.base.DataSubmissionViewModel
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
@@ -11,22 +12,22 @@ import h_mal.appttude.com.driver.utils.Coroutines.io
class VehicleProfileViewModel(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
storage: FirebaseStorageSource
) : DataSubmissionBaseViewModel<VehicleProfile>(auth, database, storage) {
database: FirebaseDatabaseSource
) : DataSubmissionViewModel<VehicleProfile>(auth, database) {
override val databaseRef: DatabaseReference = database.getVehicleDetailsRef(uid)
override val storageRef: StorageReference? = null
override val objectName: String = "vehicle profile"
override fun getDataFromDatabase() = retrieveDataFromDatabase<VehicleProfile>()
override fun validateData(data: VehicleProfile): Boolean {
data.colour.validateStringOrThrow("Vehicle colour")
data.model.validateStringOrThrow("Vehicle model")
data.reg.validateStringOrThrow("Vehicle registration plate")
data.make.validateStringOrThrow("Vehicle make")
data.keeperAddress.validateStringOrThrow("Keeper address")
data.keeperName.validateStringOrThrow("Keeper name")
data.keeperPostCode.validateStringOrThrow("Keeper post code")
override fun setDataInDatabase(data: VehicleProfile) {
io {
doTryOperation("Failed to upload $objectName") {
postDataToDatabase(data)
}
}
return true
}

View File

@@ -0,0 +1,46 @@
package h_mal.appttude.com.driver.viewmodels
import com.google.firebase.database.DatabaseReference
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.base.DataSubmissionBaseViewModel
import h_mal.appttude.com.driver.base.DataSubmissionViewModel
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.model.VehicleProfile
import h_mal.appttude.com.driver.utils.Coroutines.io
import java.io.IOException
class VehicleProfileViewModel2(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource
) : DataSubmissionViewModel<VehicleProfile>(auth, database) {
override val databaseRef: DatabaseReference = database.getVehicleDetailsRef(uid)
override fun validateData(data: VehicleProfile): Boolean {
if (data.model.isNullOrEmpty()) {
throw IOException("Vehicle model cannot be empty")
}
if (data.reg.isNullOrEmpty()) {
throw IOException("Vehicle registration cannot be empty")
}
if (data.make.isNullOrEmpty()) {
throw IOException("Vehicle make cannot be empty")
}
if (data.colour.isNullOrEmpty()) {
throw IOException("Vehicle colour cannot be empty")
}
if (data.keeperName.isNullOrEmpty()) {
throw IOException("Keepers name cannot be empty")
}
if (data.keeperAddress.isNullOrEmpty()) {
throw IOException("Keepers address cannot be empty")
}
if (data.keeperPostCode.isNullOrEmpty()) {
throw IOException("Keepers post code cannot be empty")
}
return true
}
}

View File

@@ -18,7 +18,7 @@ import org.kodein.di.generic.instance
abstract class BaseFragment<V : BaseViewModel, VB : ViewBinding> : Fragment(), KodeinAware {
private var _binding: VB? = null
private val binding: VB
val binding: VB
get() = _binding ?: error("Must only access binding while fragment is attached.")
var mActivity: BaseActivity<V, *>? = null

View File

@@ -1,7 +1,6 @@
package h_mal.appttude.com.driver.base
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.EditText
@@ -9,11 +8,9 @@ import androidx.core.widget.doAfterTextChanged
import androidx.viewbinding.ViewBinding
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.data.UserAuthState
import h_mal.appttude.com.driver.model.Model
import h_mal.appttude.com.driver.ui.user.LoginActivity
import h_mal.appttude.com.driver.utils.GenericsHelper.getGenericClassAt
import h_mal.appttude.com.driver.utils.TextValidationUtils.validateEditText
import h_mal.appttude.com.driver.utils.setGlideImage
import kotlin.reflect.full.createInstance
abstract class DataSubmissionBaseFragment<V : DataSubmissionBaseViewModel<T>, VB : ViewBinding, T : Model> :

View File

@@ -53,8 +53,7 @@ abstract class DataSubmissionBaseViewModel<T : Any>(
return localImageUri?.let { uri ->
storageRef?.let {
val image = storage?.uploadImage(uri, it, imageString)
image.toString()
storage!!.uploadImageReturnRef(uri, it, imageString)
}
}
}
@@ -67,6 +66,14 @@ abstract class DataSubmissionBaseViewModel<T : Any>(
return uploadImage(localImageUri) ?: imageUrl!!
}
suspend fun getImageStorageRefAfterUpload(localImageUri: Uri?): String {
if (localImageUri == null) {
throw IOException("No image is selected")
}
return uploadImage(localImageUri) ?: throw IOException("Image ")
}
suspend fun getImageUrls(localImageUris: List<Uri?>?): List<String?> {
if (localImageUris.isNullOrEmpty()) {
throw IOException("No images is selected")

View File

@@ -2,17 +2,22 @@ package h_mal.appttude.com.driver.base
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ValueEventListener
import h_mal.appttude.com.driver.data.DataState
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseCompletion
import h_mal.appttude.com.driver.data.FirebaseDataLiveData
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.GenericsHelper.getGenericClassAt
import h_mal.appttude.com.driver.utils.toLiveData
import java.io.IOException
abstract class DataSubmissionViewModel<T : Any>(
abstract class DataSubmissionViewModel<T : Document>(
auth: FirebaseAuthentication,
private val database: FirebaseDatabaseSource
) : BaseViewModel() {
@@ -21,15 +26,33 @@ abstract class DataSubmissionViewModel<T : Any>(
abstract val databaseRef: DatabaseReference
private val refLiveData: LiveData<DataState> by lazy { databaseRef.toLiveData<T>() }
private val refLiveData: LiveData<DataState> by lazy { FirebaseDataLiveData<T>(databaseRef, getGenericClassAt<T>(0).java) }
@Suppress("UNCHECKED_CAST")
private val observer = Observer<DataState> {
when (it) {
is DataState.HasData<*> -> onSuccess(it.data)
is DataState.HasData<*> -> {
if (it.data is Document) setData(it.data as T)
}
is DataState.HasError -> onError(it.error.message)
}
}
init {
private val valueListener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
snapshot.getValue(getGenericClassAt<T>(0).javaObjectType)
}
override fun onCancelled(error: DatabaseError) {
}
}
open fun setData(data: T) {
onSuccess(data)
}
fun init() {
// databaseRef.addValueEventListener()
refLiveData.observeForever(observer)
}
@@ -51,13 +74,19 @@ abstract class DataSubmissionViewModel<T : Any>(
open fun postDataToDatabase(data: T) {
io {
if (!validateData(data)) return@io
if (getDataFromDatabase() == data) return@io
doTryOperation("Failed to submit document") {
if (!validateData(data)) return@doTryOperation
val postObject = database.postToDatabaseRed(databaseRef, data)
onSuccess(postObject)
}
}
}
fun String?.validateStringOrThrow(fieldName: String) {
if (isNullOrEmpty()) {
throw IOException("$fieldName cannot be empty")
}
}
}

View File

@@ -1,32 +1,38 @@
package h_mal.appttude.com.driver.base
import android.net.Uri
import com.google.android.gms.tasks.Tasks
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.ImageDocumentFile
import h_mal.appttude.com.driver.data.ImageResults
import h_mal.appttude.com.driver.data.Storage
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.DateUtils
import h_mal.appttude.com.driver.utils.isNotNull
import kotlinx.coroutines.tasks.await
abstract class DataSubmissionViewModel2<T : Any>(
abstract class DataSubmissionViewModel2<T : ImageDocument>(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
private val storage: FirebaseStorageSource
private val storage: FirebaseStorageSource,
private val storageType: Storage
) : DataSubmissionViewModel<T>(auth, database) {
abstract val storageRef: StorageReference
// retrieve image and thumbnail as a pair
fun getImageAndThumbnail(filename: String): Pair<StorageReference, StorageReference> {
fun getImageAndThumbnail(filename: String): ImageResults {
val thumbnail = StringBuilder()
.append("thumb_")
.append(filename.split(".")[0])
.append(".png")
.toString()
return Pair(
return ImageResults(
storageRef.child(filename),
storageRef.child(thumbnail)
)
@@ -42,14 +48,44 @@ abstract class DataSubmissionViewModel2<T : Any>(
return storage.uploadImageReturnName(localImageUri, storageRef, imageString)
}
fun postDataToDatabase(localImageUri: Uri, data: T, storages: Storage) {
fun postDataToDatabase(localImageUri: Uri?, data: T) {
io {
val fileName = uploadImage(localImageUri, storages)
val afterData = setDataAfterUpload(data, fileName)
val documentFileName = data.getImageFileName()
// Validate at least image selector image or previously uploaded documents photo available
if (localImageUri == null && documentFileName.isNullOrEmpty()) {
onError("Please select an image")
return@io
}
// Upload a new image or keep old image
val fileName = localImageUri?.let { uploadImage(it, storageType) } ?: documentFileName
super.postDataToDatabase(afterData)
fileName.isNotNull {
data.setImageFileName(it)
super.postDataToDatabase(data)
return@io
}
onError("Could not upload document")
}
}
abstract fun setDataAfterUpload(data: T, filename: String): T
override fun postDataToDatabase(data: T) {}
override fun setData(data: T) {
val fileName = data.getImageFileName()
io {
fileName?.let {
val pair = getImageAndThumbnail(it)
val img = if (pair.image?.downloadUrl?.await() != null) pair.image else null
val tmb = if (pair.thumbnail?.downloadUrl?.await() != null) pair.image else null
val image = ImageResults(img, tmb)
val document = ImageDocumentFile(data, image)
onSuccess(document)
}
super.setData(data)
}
}
}

View File

@@ -1,26 +1,30 @@
package h_mal.appttude.com.driver.base
import android.net.Uri
import com.google.android.gms.tasks.Tasks
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.data.FirebaseAuthentication
import h_mal.appttude.com.driver.data.FirebaseDatabaseSource
import h_mal.appttude.com.driver.data.FirebaseStorageSource
import h_mal.appttude.com.driver.data.ImageCollection
import h_mal.appttude.com.driver.data.Storage
import h_mal.appttude.com.driver.utils.Coroutines.io
import h_mal.appttude.com.driver.utils.DateUtils
import h_mal.appttude.com.driver.utils.isNotNull
import h_mal.appttude.com.driver.utils.mapIndexSuspend
abstract class DataSubmissionViewModel3<T : Any>(
abstract class DataSubmissionViewModel3<T : MultiImageDocument>(
auth: FirebaseAuthentication,
database: FirebaseDatabaseSource,
private val storage: FirebaseStorageSource
private val storage: FirebaseStorageSource,
private val storageType: Storage
) : DataSubmissionViewModel<T>(auth, database) {
abstract val storageRef: StorageReference
fun getImagesAndThumbnails(filenames: List<String>): List<Pair<StorageReference, StorageReference>> {
return filenames.map {
fun getImagesAndThumbnails(filenames: List<String>): ImageCollection {
val listMap = filenames.map {
val thumbnail = StringBuilder()
.append("thumb_")
.append(it.split(".")[0])
@@ -32,6 +36,7 @@ abstract class DataSubmissionViewModel3<T : Any>(
storageRef.child(thumbnail)
)
}
return ImageCollection(listMap)
}
suspend fun uploadImages(localImageUris: List<Uri>, storages: Storage): List<String> {
@@ -47,15 +52,43 @@ abstract class DataSubmissionViewModel3<T : Any>(
}
}
fun postDataToDatabase(localImageUris: List<Uri>, data: T, storages: Storage) {
fun postDataToDatabase(localImageUris: List<Uri>, data: T) {
io {
val files = uploadImages(localImageUris, storages)
val postData = setDataAfterUpload(data, files)
// Validate document fore upload
if (!validateData(data)) return@io
val documentFileNames = data.getImageFileNames()
// Validate at least image selector image or previously uploaded documents photo available
if (localImageUris.isEmpty() && documentFileNames.isNullOrEmpty()) {
onError("Please select an image")
return@io
}
// Upload a new image or keep old image
val fileNames =
localImageUris.takeIf { it.isNotEmpty() }?.let { uploadImages(it, storageType) }
?: documentFileNames
super.postDataToDatabase(postData)
fileNames.isNotNull {
data.setImageFileNames(it)
super.postDataToDatabase(data)
return@io
}
onError("Could not upload document")
}
}
abstract fun setDataAfterUpload(data: T, filename: List<String>): T
override fun setData(data: T) {
val fileNames = data.getImageFileNames()
fileNames?.let { l ->
val list = getImagesAndThumbnails(l).collection
val results = list.map { it.second.downloadUrl }.let { Tasks.whenAllComplete(it) }
results.addOnSuccessListener {
onSuccess(list)
}
}
super.setData(data)
}
override fun postDataToDatabase(data: T) {}
}

View File

@@ -0,0 +1,73 @@
package h_mal.appttude.com.driver.base
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.EditText
import androidx.core.widget.doAfterTextChanged
import androidx.viewbinding.ViewBinding
import h_mal.appttude.com.driver.data.UserAuthState
import h_mal.appttude.com.driver.ui.user.LoginActivity
import h_mal.appttude.com.driver.utils.GenericsHelper.getGenericClassAt
import h_mal.appttude.com.driver.utils.TextValidationUtils.validateEditText
import kotlin.reflect.full.createInstance
abstract class FormSubmissionFragment<V : DataSubmissionViewModel<T>, VB : ViewBinding, T : Document> :
BaseFragment<V, VB>() {
var model: T = getGenericClassAt<T>(2).createInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.init()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// If user is logged out then navigate to LoginActivity
viewModel.stateLiveData.observe(viewLifecycleOwner) {
if (it is UserAuthState.LoggedOut) {
val intent = Intent(requireContext(), LoginActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
requireActivity().finish()
return@observe
}
}
}
@Suppress("UNCHECKED_CAST")
override fun onSuccess(data: Any?) {
super.onSuccess(data)
when (data) {
is Model -> {
model = data as T
setFields(data)
}
}
}
open fun setFields(data: T) { }
open fun submit() {}
fun validateEditTexts(vararg editTexts: EditText): Boolean {
editTexts.forEach {
if (it.text.isNullOrBlank()) {
it.validateEditText()
return false
}
}
return true
}
fun EditText.setTextOnChange(output: (m: String) -> Unit) {
doAfterTextChanged {
output(text.toString())
}
}
open fun submitDocument() {
viewModel.postDataToDatabase(model)
}
}

View File

@@ -0,0 +1,96 @@
package h_mal.appttude.com.driver.base
import android.Manifest
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts
import androidx.viewbinding.ViewBinding
import com.google.firebase.storage.StorageReference
import h_mal.appttude.com.driver.data.ImageDocumentFile
import h_mal.appttude.com.driver.data.ImageResults
import h_mal.appttude.com.driver.ui.permission.PermissionsDeclarationDialog
import permissions.dispatcher.NeedsPermission
import permissions.dispatcher.OnNeverAskAgain
import permissions.dispatcher.OnPermissionDenied
import permissions.dispatcher.OnShowRationale
import permissions.dispatcher.PermissionRequest
import permissions.dispatcher.RuntimePermissions
import java.util.concurrent.atomic.AtomicReference
@RuntimePermissions
abstract class ImageFormSubmissionFragment<V : DataSubmissionViewModel2<T>, VB : ViewBinding, T : ImageDocument> :
FormSubmissionFragment<V, VB, T>(), ImageSelectionHelper {
private val selectedImage: AtomicReference<Uri> = AtomicReference()
private fun setSelectedImages(image: Uri) {
selectedImage.set(image)
}
fun getSelectedImages(): Uri? {
return selectedImage.get()
}
override fun onSuccess(data: Any?) {
super.onSuccess(data)
if (data is ImageDocumentFile<*>) {
setImage(data.image.image, data.image.thumbnail)
setFields(data.document as T)
}
}
open fun setImage(image: StorageReference?, thumbnail: StorageReference?) {}
private val permissionRequest =
registerForActivityResult(ActivityResultContracts.GetContent()) { result ->
result?.let { onImageGalleryResult(result) }
}
override fun openGalleryForImageSelection() {
permissionRequest.launch("image/*")
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// NOTE: delegate the permission handling to generated method
onRequestPermissionsResult(requestCode, grantResults)
}
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
fun showStorage() {
openGalleryForImageSelection()
}
@OnShowRationale(Manifest.permission.READ_EXTERNAL_STORAGE)
fun showRationaleForStorage(request: PermissionRequest) {
PermissionsDeclarationDialog(requireContext()).showDialog({
request.proceed()
}, {
request.cancel()
})
}
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
fun onStorageDenied() {
showToast("Storage permissions have been denied")
}
@OnNeverAskAgain(Manifest.permission.READ_EXTERNAL_STORAGE)
fun onStorageNeverAskAgain() {
showToast("Storage permissions have been to never ask again")
}
/**
* Called on the result of image selection
*/
open fun onImageGalleryResult(imageUri: Uri) {
setSelectedImages(imageUri)
}
override fun submitDocument() {
viewModel.postDataToDatabase(getSelectedImages(), model)
}
}

View File

@@ -0,0 +1,27 @@
package h_mal.appttude.com.driver.base
import android.content.ClipData
import android.content.Intent
import android.net.Uri
interface ImageSelectionHelper {
fun openGalleryForImageSelection()
fun imageSelectorIntent(multiImage: Boolean = false) = Intent(Intent.ACTION_GET_CONTENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiImage)
.setType("image/*")
fun Intent?.parseMultiImageIntent(): List<Uri> {
this?.clipData?.takeIf { it.itemCount > 1 }?.convertToList()?.let { clip ->
val list = clip.takeIf { it.size > 10 }?.let {
clip.subList(0, 9)
} ?: clip
return list
}
return listOfNotNull(this?.data)
}
private fun ClipData.convertToList(): List<Uri> = 0.rangeTo(itemCount).map { getItemAt(it).uri }
}

View File

@@ -16,14 +16,11 @@ import permissions.dispatcher.PermissionRequest
import permissions.dispatcher.RuntimePermissions
@RuntimePermissions
open class ImageSelectorFragment<V : BaseViewModel, VB : ViewBinding> : BaseFragment<V, VB>() {
private var multipleImage: Boolean = false
open class ImageSelectorFragment<V : BaseViewModel, VB : ViewBinding>(
private val multipleImage: Boolean = false
) : BaseFragment<V, VB>() {
var picUri: Uri? = null
fun setImageSelectionAsMultiple() {
multipleImage = true
}
fun openGalleryForImage() {
permissionRequest.launch(multipleImage)
}

View File

@@ -0,0 +1,14 @@
package h_mal.appttude.com.driver.base
interface Model
interface Document : Model
interface ImageDocument : Document {
fun getImageFileName(): String?
fun setImageFileName(fileName: String)
}
interface MultiImageDocument : Document {
fun getImageFileNames(): List<String>?
fun setImageFileNames(images: List<String>)
}

View File

@@ -0,0 +1,94 @@
package h_mal.appttude.com.driver.base
import android.Manifest
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts
import androidx.viewbinding.ViewBinding
import h_mal.appttude.com.driver.data.ImageCollection
import h_mal.appttude.com.driver.ui.permission.PermissionsDeclarationDialog
import permissions.dispatcher.NeedsPermission
import permissions.dispatcher.OnNeverAskAgain
import permissions.dispatcher.OnPermissionDenied
import permissions.dispatcher.OnShowRationale
import permissions.dispatcher.PermissionRequest
import permissions.dispatcher.RuntimePermissions
@RuntimePermissions
abstract class MultiImageFormSubmissionFragment3<V : DataSubmissionViewModel3<T>, VB : ViewBinding, T : MultiImageDocument> :
FormSubmissionFragment<V, VB, T>(), ImageSelectionHelper {
private val selectedImages: MutableList<Uri> = mutableListOf()
private fun setSelectedImages(images: List<Uri>) {
selectedImages.clear()
selectedImages.addAll(images)
}
fun getSelectedImages(): List<Uri> {
return selectedImages
}
override fun onSuccess(data: Any?) {
super.onSuccess(data)
if (data is ImageCollection) {
setImages(data)
}
}
open fun setImages(collection: ImageCollection) {}
private val permissionRequest =
registerForActivityResult(ActivityResultContracts.GetMultipleContents()) { result ->
result?.let { onImageGalleryResult(result) }
}
override fun openGalleryForImageSelection() {
permissionRequest.launch("image/*")
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// NOTE: delegate the permission handling to generated method
onRequestPermissionsResult(requestCode, grantResults)
}
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
fun showStorage() {
openGalleryForImageSelection()
}
@OnShowRationale(Manifest.permission.READ_EXTERNAL_STORAGE)
fun showRationaleForStorage(request: PermissionRequest) {
PermissionsDeclarationDialog(requireContext()).showDialog({
request.proceed()
}, {
request.cancel()
})
}
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
fun onStorageDenied() {
showToast("Storage permissions have been denied")
}
@OnNeverAskAgain(Manifest.permission.READ_EXTERNAL_STORAGE)
fun onStorageNeverAskAgain() {
showToast("Storage permissions have been to never ask again")
}
/**
* Called on the result of image selection
*/
open fun onImageGalleryResult(imageUris: List<Uri>) {
setSelectedImages(imageUris)
}
override fun submitDocument() {
viewModel.postDataToDatabase(getSelectedImages(), model)
}
}

View File

@@ -0,0 +1,37 @@
package h_mal.appttude.com.driver.data
import androidx.lifecycle.LiveData
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ValueEventListener
/**
* Creates #LiveDate out of {UserAuthState} for firebase user state
*/
class FirebaseDataLiveData<T : Any>(
private val reference: DatabaseReference,
private val cls: Class<T>
) : LiveData<DataState>() {
override fun onActive() {
super.onActive()
reference.addValueEventListener(stateListener)
}
override fun onInactive() {
super.onInactive()
reference.addValueEventListener(stateListener)
}
private val stateListener = object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val data = snapshot.getValue(cls)
postValue(DataState.HasData(data ?: FirebaseCompletion.Default))
}
override fun onCancelled(error: DatabaseError) {
postValue(DataState.HasError(error))
}
}
}

View File

@@ -2,6 +2,7 @@ package h_mal.appttude.com.driver.data
import android.net.Uri
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageMetadata
import com.google.firebase.storage.StorageReference
import kotlinx.coroutines.tasks.await
@@ -27,7 +28,7 @@ class FirebaseStorageSource {
suspend fun uploadImageReturnName(localFilePath: Uri, path: StorageReference, filename: String): String {
val ref = path.child("$filename.jpeg")
return ref.putFile(localFilePath)
return ref.putFile(localFilePath, StorageMetadata.Builder().setContentType("image/jpeg").build())
.continueWith { ref.name }
.await()
}

View File

@@ -0,0 +1,8 @@
package h_mal.appttude.com.driver.data
import h_mal.appttude.com.driver.base.ImageDocument
data class ImageDocumentFile<T : ImageDocument>(
val document: T,
val image: ImageResults
)

View File

@@ -0,0 +1,12 @@
package h_mal.appttude.com.driver.data
import com.google.firebase.storage.StorageReference
data class ImageResults(
val image: StorageReference?,
val thumbnail: StorageReference?
)
data class ImageCollection(
val collection: List<Pair<StorageReference, StorageReference>>
)

View File

@@ -0,0 +1,14 @@
package h_mal.appttude.com.driver.data
import com.google.firebase.database.DataSnapshot
import h_mal.appttude.com.driver.utils.GenericsHelper.getGenericClassAt
class SnapshotResults<T: Any>(
private val snapshot: DataSnapshot
) {
private val cls = this.getGenericClassAt<T>(0).getGenericClassAt<T>(0).java
fun getData(): T? {
return snapshot.getValue(cls)
}
}

View File

@@ -1,5 +1,11 @@
package h_mal.appttude.com.driver.model
import com.google.firebase.database.Exclude
import com.google.firebase.database.IgnoreExtraProperties
import h_mal.appttude.com.driver.base.Document
import h_mal.appttude.com.driver.base.ImageDocument
@IgnoreExtraProperties
data class DriverProfile(
var driverPic: String? = null,
var forenames: String? = null,
@@ -8,4 +14,14 @@ data class DriverProfile(
var dob: String? = null,
var ni: String? = null,
var dateFirst: String? = null
) : Model
) : ImageDocument {
@Exclude
override fun getImageFileName(): String? {
return driverPic
}
@Exclude
override fun setImageFileName(fileName: String) {
driverPic = fileName
}
}

View File

@@ -1,8 +1,22 @@
package h_mal.appttude.com.driver.model
import com.google.firebase.database.Exclude
import com.google.firebase.database.IgnoreExtraProperties
import h_mal.appttude.com.driver.base.ImageDocument
@IgnoreExtraProperties
data class DriversLicense(
var licenseImageString: String? = null,
var licenseNumber: String? = null,
var licenseExpiry: String? = null
) : Model
) : ImageDocument {
@Exclude
override fun getImageFileName(): String? {
return licenseImageString
}
@Exclude
override fun setImageFileName(fileName: String) {
licenseImageString = fileName
}
}

View File

@@ -1,7 +1,31 @@
package h_mal.appttude.com.driver.model
import com.google.firebase.database.Exclude
import com.google.firebase.database.IgnoreExtraProperties
import h_mal.appttude.com.driver.base.MultiImageDocument
@IgnoreExtraProperties
data class Insurance(
var photoStrings: MutableList<String>? = null,
var insurerName: String? = null,
var expiryDate: String? = null
) : Model
) : MultiImageDocument {
@Exclude
override fun getImageFileNames(): List<String>? {
return photoStrings
}
@Exclude
override fun setImageFileNames(images: List<String>) {
// update existing array
photoStrings?.run{
clear()
addAll(images)
return
}
// set new array of images
photoStrings = mutableListOf<String>().apply {
addAll(images)
}
}
}

View File

@@ -1,7 +1,20 @@
package h_mal.appttude.com.driver.model
import com.google.firebase.database.Exclude
import h_mal.appttude.com.driver.base.Document
import h_mal.appttude.com.driver.base.ImageDocument
data class Logbook(
var photoString: String? = null,
var v5cnumber: String? = null
) : Model
) : ImageDocument{
@Exclude
override fun getImageFileName(): String? {
return photoString
}
@Exclude
override fun setImageFileName(fileName: String) {
photoString = fileName
}
}

View File

@@ -1,3 +0,0 @@
package h_mal.appttude.com.driver.model
interface Model

View File

@@ -1,7 +1,21 @@
package h_mal.appttude.com.driver.model
import com.google.firebase.database.Exclude
import com.google.firebase.database.IgnoreExtraProperties
import h_mal.appttude.com.driver.base.ImageDocument
@IgnoreExtraProperties
data class Mot(
var motImageString: String? = null,
var motExpiry: String? = null
) : Model
) : ImageDocument {
@Exclude
override fun getImageFileName(): String? {
return motImageString
}
@Exclude
override fun setImageFileName(fileName: String) {
motImageString = fileName
}
}

View File

@@ -1,8 +1,23 @@
package h_mal.appttude.com.driver.model
import com.google.firebase.database.Exclude
import com.google.firebase.database.IgnoreExtraProperties
import h_mal.appttude.com.driver.base.ImageDocument
@IgnoreExtraProperties
data class PrivateHireLicense(
var phImageString: String? = null,
var phNumber: String? = null,
var phExpiry: String? = null
) : Model
) : ImageDocument {
@Exclude
override fun getImageFileName(): String? {
return phImageString
}
@Exclude
override fun setImageFileName(fileName: String) {
phImageString = fileName
}
}

View File

@@ -1,8 +1,23 @@
package h_mal.appttude.com.driver.model
import com.google.firebase.database.Exclude
import com.google.firebase.database.IgnoreExtraProperties
import h_mal.appttude.com.driver.base.ImageDocument
@IgnoreExtraProperties
data class PrivateHireVehicle(
var phCarImageString: String? = null,
var phCarNumber: String? = null,
var phCarExpiry: String? = null
) : Model
) : ImageDocument {
@Exclude
override fun getImageFileName(): String? {
return phCarImageString
}
@Exclude
override fun setImageFileName(fileName: String) {
phCarImageString = fileName
}
}

View File

@@ -1,6 +1,9 @@
package h_mal.appttude.com.driver.model
import com.google.firebase.database.IgnoreExtraProperties
import h_mal.appttude.com.driver.base.Document
@IgnoreExtraProperties
data class VehicleProfile(
var reg: String? = null,
var make: String? = null,
@@ -11,4 +14,4 @@ data class VehicleProfile(
var keeperPostCode: String? = null,
var startDate: String? = null,
var isSeized: Boolean = false
) : Model
) : Document

View File

@@ -76,7 +76,7 @@ suspend fun <T : Any> DatabaseReference.getDataFromDatabaseRef(clazz: Class<T>):
fun <T : Any> DatabaseReference.toLiveData(): LiveData<DataState> {
return object : LiveData<DataState>() {
private val listener = addValueEventListener(object : ValueEventListener{
private val listener = object : ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
val data = snapshot.getValue(object : GenericTypeIndicator<T>() {})
postValue(DataState.HasData(data ?: FirebaseCompletion.Default))
@@ -84,7 +84,7 @@ fun <T : Any> DatabaseReference.toLiveData(): LiveData<DataState> {
override fun onCancelled(error: DatabaseError) {
postValue(DataState.HasError(error))
}
})
}
override fun onActive() {
super.onActive()
// add listener

View File

@@ -12,7 +12,7 @@ object GenericsHelper {
((javaClass.genericSuperclass as? ParameterizedType)
?.actualTypeArguments?.getOrNull(position) as? Class<CLASS>)
?.kotlin
?: throw IllegalStateException("Can not find class from generic argument")
?: throw IllegalStateException("Can not find class from generic argument ${this}")
/**
* Create a view binding out of the the generic [VB]

View File

@@ -4,7 +4,7 @@
"port": 9099
},
"database": {
"port": 9000
"port": 9001
},
"storage": {
"port": 9199
@@ -12,12 +12,27 @@
"ui": {
"enabled": true
},
"singleProjectMode": true
"singleProjectMode": true,
"functions": {
"port": 5001
}
},
"database": {
"rules": "database.rules.json"
},
"storage": {
"rules": "storage.rules"
},
"functions": [
{
"source": "functions",
"codebase": "default",
"ignore": [
"venv",
".git",
"firebase-debug.log",
"firebase-debug.*.log"
]
}
]
}