diff --git a/business-logic/src/main/java/eu/europa/ec/businesslogic/validator/FormValidator.kt b/business-logic/src/main/java/eu/europa/ec/businesslogic/validator/FormValidator.kt index e9da04e7..12fc6ef2 100644 --- a/business-logic/src/main/java/eu/europa/ec/businesslogic/validator/FormValidator.kt +++ b/business-logic/src/main/java/eu/europa/ec/businesslogic/validator/FormValidator.kt @@ -16,6 +16,7 @@ package eu.europa.ec.businesslogic.validator +import android.net.Uri import android.util.Patterns import com.google.i18n.phonenumbers.PhoneNumberUtil import eu.europa.ec.businesslogic.controller.log.LogController @@ -24,17 +25,21 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.withContext interface FormValidator { - fun validateForm(form: Form): Flow - fun validateForms(forms: List
): Flow + fun validateFormFlow(form: Form): Flow + fun validateFormsFlow(forms: List): Flow + + suspend fun validateForm(form: Form): FormValidationResult + suspend fun validateForms(forms: List): FormsValidationResult } class FormValidatorImpl( private val logController: LogController ) : FormValidator { - override fun validateForm(form: Form): Flow = flow { + override fun validateFormFlow(form: Form): Flow = flow { form.inputs.forEach { (rules, value) -> rules.forEach { rule -> validateRule(rule, value)?.let { @@ -46,7 +51,20 @@ class FormValidatorImpl( emit(FormValidationResult(isValid = true)) }.flowOn(Dispatchers.IO) - override fun validateForms(forms: List): Flow = flow { + override suspend fun validateForm(form: Form): FormValidationResult = + withContext(Dispatchers.IO) { + for (input in form.inputs) { + val (rules, value) = input + for (rule in rules) { + validateRule(rule, value)?.let { + return@withContext it + } + } + } + return@withContext FormValidationResult(isValid = true) + } + + override fun validateFormsFlow(forms: List): Flow = flow { val errors = mutableListOf() var isValid = true forms.forEach { form -> @@ -62,9 +80,32 @@ class FormValidatorImpl( emit(FormsValidationResult(isValid, errors)) }.flowOn(Dispatchers.IO) + override suspend fun validateForms(forms: List): FormsValidationResult = + withContext(Dispatchers.IO) { + val errorMessages = mutableListOf() + var allValid = true + for (form in forms) { + for (input in form.inputs) { + val (rules, value) = input + for (rule in rules) { + validateRule(rule, value)?.let { + allValid = false + errorMessages.add(it.message) + } + } + } + } + return@withContext FormsValidationResult(allValid, errorMessages) + } + private fun validateRule(rule: Rule, value: String): FormValidationResult? { return when (rule) { is Rule.ValidateEmail -> checkValidationResult(isEmailValid(value), rule.errorMessage) + is Rule.ValidateProjectUrl -> checkValidationResult( + isValidProjectUrl(value), + rule.errorMessage + ) + is Rule.ValidatePhoneNumber -> checkValidationResult( isPhoneNumberValid(value, rule.countryCode), rule.errorMessage @@ -180,6 +221,16 @@ class FormValidatorImpl( private fun isEmailValid(value: String): Boolean = value.isNotEmpty() && Patterns.EMAIL_ADDRESS.matcher(value).matches() + private fun isValidProjectUrl(value: String): Boolean { + if (value.isEmpty()) return false + return try { + val uri = Uri.parse(Uri.decode(value)) + !uri.scheme.isNullOrEmpty() && !uri.host.isNullOrEmpty() && !uri.query.isNullOrEmpty() + } catch (e: Exception) { + false + } + } + private fun isPhoneNumberValid(value: String, countryCode: String): Boolean { val phoneNumberUtil = PhoneNumberUtil.getInstance() return try { @@ -301,6 +352,7 @@ data class FormsValidationResult(val isValid: Boolean, val messages: List() { @@ -87,10 +102,7 @@ class QrScanViewModel( copy(finishedScanning = true) } - calculateNextStep( - qrScanFlow = viewState.value.qrScannedConfig.qrScanFlow, - scanResult = event.resultQr - ) + handleScannedQr(event.resultQr) } is Event.CameraAccessGranted -> { @@ -109,6 +121,48 @@ class QrScanViewModel( } } + private fun handleScannedQr(scannedQr: String) { + viewModelScope.launch { + val urlIsValid = validateForm( + form = Form( + inputs = mapOf( + listOf( + Rule.ValidateProjectUrl(errorMessage = "") + ) to scannedQr + ) + ) + ) + + if (urlIsValid) { + calculateNextStep( + qrScanFlow = viewState.value.qrScannedConfig.qrScanFlow, + scanResult = scannedQr + ) + } else if (viewState.value.failedScanAttempts < MAX_ALLOWED_FAILED_SCANS) { + setState { + copy( + failedScanAttempts = viewState.value.failedScanAttempts + 1, + finishedScanning = false + ) + } + } else { + setState { + copy( + showInformativeText = true, + informativeText = calculateInformativeText(viewState.value.qrScannedConfig.qrScanFlow) + ) + } + } + } + } + + private suspend fun validateForm(form: Form): Boolean { + val validationResult = interactor.validateForm( + form = form, + ) + return validationResult.isValid + } + private fun calculateNextStep( qrScanFlow: QrScanFlow, scanResult: String, @@ -119,6 +173,17 @@ class QrScanViewModel( } } + private fun calculateInformativeText( + qrScanFlow: QrScanFlow, + ): String { + return with(resourceProvider) { + when (qrScanFlow) { + is QrScanFlow.Presentation -> getString(R.string.qr_scan_informative_text_presentation_flow) + is QrScanFlow.Issuance -> getString(R.string.qr_scan_informative_text_issuance_flow) + } + } + } + private fun navigateToPresentationRequest(scanResult: String) { setEffect { getOrCreatePresentationScope() diff --git a/resources-logic/src/main/res/values/strings.xml b/resources-logic/src/main/res/values/strings.xml index f3599ddd..75d0c9bf 100644 --- a/resources-logic/src/main/res/values/strings.xml +++ b/resources-logic/src/main/res/values/strings.xml @@ -182,6 +182,8 @@ Camera permission not provided\nOpen App Settings + Having trouble scanning the QR Code?\nPlease try an alternative way + Having trouble scanning the QR Code?\nPlease try an alternative way Shown above