From 7a060f3187f24584c7751e4cd36c801f627bd93f Mon Sep 17 00:00:00 2001 From: Balrambhai Sharma Date: Wed, 20 Nov 2024 19:46:23 +0530 Subject: [PATCH] NTP-5471 | Abha aadhar fix (#13683) Co-authored-by: Shivam Goyal --- .../java/com/navi/base/utils/AadhaarUtils.kt | 57 ++++++++++++++ .../java/com/navi/base/AadhaarUtilsTest.kt | 73 ++++++++++++++++++ .../com/navi/insurance/abha/ABHAFragmentVM.kt | 42 ++++++++-- .../navi/insurance/abha/ABHAaadharFragment.kt | 76 ++++++++++--------- .../GenericComposableWidgetFactory.kt | 6 +- .../model/EditableTextWidgetData.kt | 1 + .../widgets/EditableTextWidgetComposable.kt | 27 ++++--- 7 files changed, 228 insertions(+), 54 deletions(-) create mode 100644 android/navi-base/src/main/java/com/navi/base/utils/AadhaarUtils.kt create mode 100644 android/navi-base/src/test/java/com/navi/base/AadhaarUtilsTest.kt diff --git a/android/navi-base/src/main/java/com/navi/base/utils/AadhaarUtils.kt b/android/navi-base/src/main/java/com/navi/base/utils/AadhaarUtils.kt new file mode 100644 index 0000000000..15d2ae0e8c --- /dev/null +++ b/android/navi-base/src/main/java/com/navi/base/utils/AadhaarUtils.kt @@ -0,0 +1,57 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.base.utils + +class AadhaarUtils { + private val d = + arrayOf( + intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), + intArrayOf(1, 2, 3, 4, 0, 6, 7, 8, 9, 5), + intArrayOf(2, 3, 4, 0, 1, 7, 8, 9, 5, 6), + intArrayOf(3, 4, 0, 1, 2, 8, 9, 5, 6, 7), + intArrayOf(4, 0, 1, 2, 3, 9, 5, 6, 7, 8), + intArrayOf(5, 9, 8, 7, 6, 0, 4, 3, 2, 1), + intArrayOf(6, 5, 9, 8, 7, 1, 0, 4, 3, 2), + intArrayOf(7, 6, 5, 9, 8, 2, 1, 0, 4, 3), + intArrayOf(8, 7, 6, 5, 9, 3, 2, 1, 0, 4), + intArrayOf(9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + ) + + private val p = + arrayOf( + intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), + intArrayOf(1, 5, 7, 6, 2, 8, 3, 0, 9, 4), + intArrayOf(5, 8, 0, 3, 7, 9, 6, 1, 4, 2), + intArrayOf(8, 9, 1, 6, 0, 4, 3, 5, 2, 7), + intArrayOf(9, 4, 5, 3, 1, 2, 6, 8, 7, 0), + intArrayOf(4, 2, 8, 6, 5, 7, 3, 9, 0, 1), + intArrayOf(2, 7, 9, 3, 8, 0, 6, 4, 1, 5), + intArrayOf(7, 0, 4, 6, 9, 1, 3, 2, 5, 8) + ) + + private val inv = intArrayOf(0, 4, 3, 2, 1, 5, 6, 7, 8, 9) + + fun validateVerhoeff(num: String): Boolean { + var charIndex = 0 + val myArray = stringToReversedIntArray(num) + for (index in myArray.indices) { + charIndex = d[charIndex][p[index % 8][myArray[index]]] + } + return charIndex == 0 + } + + fun stringToReversedIntArray(num: String): IntArray { + val myArray = num.map { it.toString().toInt() }.toIntArray() + return myArray.reversedArray() + } + + fun isValidAadhaar(aadhaarNumber: String): Boolean { + val aadhaarPattern = Regex("\\d{12}") + return aadhaarPattern.matches(aadhaarNumber) && validateVerhoeff(aadhaarNumber) + } +} diff --git a/android/navi-base/src/test/java/com/navi/base/AadhaarUtilsTest.kt b/android/navi-base/src/test/java/com/navi/base/AadhaarUtilsTest.kt new file mode 100644 index 0000000000..e7b7ea0a57 --- /dev/null +++ b/android/navi-base/src/test/java/com/navi/base/AadhaarUtilsTest.kt @@ -0,0 +1,73 @@ +/* + * + * * Copyright © 2024 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.base + +import com.navi.base.utils.AadhaarUtils +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class AadhaarUtilsTest { + private val aadhaarUtils = AadhaarUtils() + + @Test + fun `validateAadhaarNumber returns true for valid Aadhaar number`() { + val validAadhaar = "273838226780" + assertTrue(aadhaarUtils.isValidAadhaar(validAadhaar)) + } + + @Test + fun `validateAadhaarNumber returns false for invalid Aadhaar number`() { + val invalidAadhaar = "123456781235" + assertFalse(aadhaarUtils.isValidAadhaar(invalidAadhaar)) + } + + @Test + fun `validateAadhaarNumber returns false for non-numeric input`() { + val invalidAadhaar = "1234A6781234" + assertFalse(aadhaarUtils.isValidAadhaar(invalidAadhaar)) + } + + @Test + fun `validateAadhaarNumber returns false for input with less than 12 digits`() { + val shortAadhaar = "123456" + assertFalse(aadhaarUtils.isValidAadhaar(shortAadhaar)) + } + + @Test + fun `validateAadhaarNumber returns false for input with more than 12 digits`() { + val longAadhaar = "1234567890123" + assertFalse(aadhaarUtils.isValidAadhaar(longAadhaar)) + } + + @Test + fun `validateVerhoeff returns true for valid Verhoeff number`() { + val validVerhoeff = "236" + assertTrue(aadhaarUtils.validateVerhoeff(validVerhoeff)) + } + + @Test + fun `validateVerhoeff returns false for invalid Verhoeff number`() { + val invalidVerhoeff = "123" + assertFalse(aadhaarUtils.validateVerhoeff(invalidVerhoeff)) + } + + @Test + fun `stringToReversedIntArray correctly reverses string to int array`() { + val input = "12345" + val expected = intArrayOf(5, 4, 3, 2, 1) + assertArrayEquals(expected, aadhaarUtils.stringToReversedIntArray(input)) + } + + @Test(expected = NumberFormatException::class) + fun `stringToReversedIntArray throws exception for non-numeric input`() { + val input = "12A45" + aadhaarUtils.stringToReversedIntArray(input) + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAFragmentVM.kt b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAFragmentVM.kt index eb96b33956..975f576049 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAFragmentVM.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAFragmentVM.kt @@ -8,6 +8,7 @@ package com.navi.insurance.abha import androidx.lifecycle.viewModelScope +import com.navi.base.utils.AadhaarUtils import com.navi.base.utils.isNotNull import com.navi.base.utils.isNull import com.navi.base.utils.orZero @@ -54,14 +55,29 @@ constructor( val footerState: StateFlow get() = _footerState.asStateFlow() + private val _errorState: MutableStateFlow = MutableStateFlow(null) + val errorState: StateFlow + get() = _errorState.asStateFlow() + private var otpPageValidation: Int = 0 private var aadharPageValidation: Int = 0 - var validAadharWidgets: HashMap = hashMapOf() + var validAadharWidgets: HashMap> = hashMapOf() var validOtpWidgets: HashMap = hashMapOf() private var policyId: String? = null private var pageSource: String? = null private var linkAbhaRequest: LinkAbhaRequest = LinkAbhaRequest() + fun updateErrorIndex(index: Int?) { + _errorState.value = index + } + + fun isValidAadhar(): Boolean { + return (validAadharWidgets[AADHAR_NUMBER_EXTRA]?.first as? EditableTextWidgetData) + ?.widgetData + ?.value + ?.let { AadhaarUtils().isValidAadhaar(it) } ?: false + } + fun setPolicyId(policyId: String?) { this.policyId = policyId } @@ -135,9 +151,13 @@ constructor( } } - fun updateAadharValidWidgets(widgetDataInfo: GenericWidgetDataInfo, isValid: Boolean) { + fun updateAadharValidWidgets( + widgetDataInfo: GenericWidgetDataInfo, + isValid: Boolean, + index: Int = -1 + ) { when { - isValid -> addValidWidget(widgetDataInfo) + isValid -> addValidWidget(widgetDataInfo, index) else -> removeValidWidget(widgetDataInfo) } @@ -149,16 +169,19 @@ constructor( } } - private fun addValidWidget(widgetDataInfo: GenericWidgetDataInfo) { + private fun addValidWidget(widgetDataInfo: GenericWidgetDataInfo, index: Int) { when (widgetDataInfo) { is EditableTextWidgetData -> { when (widgetDataInfo.widgetData?.id) { - AADHAR_NUMBER_EXTRA -> validAadharWidgets[AADHAR_NUMBER_EXTRA] = widgetDataInfo - MOBILE_NUMBER_EXTRA -> validAadharWidgets[MOBILE_NUMBER_EXTRA] = widgetDataInfo + AADHAR_NUMBER_EXTRA -> + validAadharWidgets[AADHAR_NUMBER_EXTRA] = Pair(widgetDataInfo, index) + MOBILE_NUMBER_EXTRA -> + validAadharWidgets[MOBILE_NUMBER_EXTRA] = Pair(widgetDataInfo, index) } } is SelectableTextWidgetData -> { - validAadharWidgets[WidgetTypes.SELECTABLE_TEXT_WIDGET.value] = widgetDataInfo + validAadharWidgets[WidgetTypes.SELECTABLE_TEXT_WIDGET.value] = + Pair(widgetDataInfo, index) } } } @@ -167,7 +190,10 @@ constructor( when (widgetDataInfo) { is EditableTextWidgetData -> { when (widgetDataInfo.widgetData?.id) { - AADHAR_NUMBER_EXTRA -> validAadharWidgets.remove(AADHAR_NUMBER_EXTRA) + AADHAR_NUMBER_EXTRA -> { + _errorState.value = null + validAadharWidgets.remove(AADHAR_NUMBER_EXTRA) + } MOBILE_NUMBER_EXTRA -> validAadharWidgets.remove(MOBILE_NUMBER_EXTRA) } } diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAaadharFragment.kt b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAaadharFragment.kt index 1a7f91d925..1d30b2e8a2 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAaadharFragment.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/abha/ABHAaadharFragment.kt @@ -139,6 +139,7 @@ class ABHAaadharFragment : BaseFragment(), WidgetCallback { @OptIn(ExperimentalFoundationApi::class) @Composable fun AbhaAadharContentScreen(data: ABHAPageResponse) { + val errorIndex = viewModel.errorState.collectAsState() val widgetCallback: WidgetCallback = this Scaffold( modifier = Modifier.fillMaxSize().background(color = Color.White), @@ -163,8 +164,9 @@ class ABHAaadharFragment : BaseFragment(), WidgetCallback { data = genericWidgetData, widgetCallback = widgetCallback, onWidgetUpdate = { data, isValid -> - viewModel.updateAadharValidWidgets(data, isValid) - } + viewModel.updateAadharValidWidgets(data, isValid, index) + }, + showError = errorIndex.value == index ) } } @@ -189,43 +191,49 @@ class ABHAaadharFragment : BaseFragment(), WidgetCallback { openHelpCenter() } CtaType.NAVIGATE_TO_NEW_SCREEN.value -> { - val params = naviClickAction?.parameters?.toMutableList() - viewModel.validAadharWidgets.forEach { widgetDataInfo -> - if ( - widgetDataInfo.value is EditableTextWidgetData && - widgetDataInfo?.key == AADHAR_NUMBER_EXTRA - ) { - params?.add( - LineItem( - AADHAR_NUMBER_EXTRA, - (widgetDataInfo.value as EditableTextWidgetData) - ?.widgetData - ?.value + if (viewModel.isValidAadhar()) { + val params = naviClickAction?.parameters?.toMutableList() + viewModel.validAadharWidgets.forEach { widgetDataInfo -> + if ( + widgetDataInfo.value.first is EditableTextWidgetData && + widgetDataInfo?.key == AADHAR_NUMBER_EXTRA + ) { + params?.add( + LineItem( + AADHAR_NUMBER_EXTRA, + (widgetDataInfo.value?.first as EditableTextWidgetData) + ?.widgetData + ?.value + ) ) - ) - } + } - if ( - widgetDataInfo.value is EditableTextWidgetData && - widgetDataInfo?.key == MOBILE_NUMBER_EXTRA - ) { - params?.add( - LineItem( - MOBILE_NUMBER_EXTRA, - (widgetDataInfo.value as EditableTextWidgetData) - ?.widgetData - ?.value + if ( + widgetDataInfo.value.first is EditableTextWidgetData && + widgetDataInfo?.key == MOBILE_NUMBER_EXTRA + ) { + params?.add( + LineItem( + MOBILE_NUMBER_EXTRA, + (widgetDataInfo.value?.first as EditableTextWidgetData) + ?.widgetData + ?.value + ) ) - ) + } } + val redirectionCta = naviClickAction.copy(parameters = params) + NaviInsuranceDeeplinkNavigator.navigate( + activity, + ctaData = redirectionCta, + finish = redirectionCta.finish.orFalse(), + clearTask = redirectionCta.clearTask.orFalse() + ) + } else { + viewModel.updateErrorIndex( + viewModel.validAadharWidgets.get(AADHAR_NUMBER_EXTRA)?.second + ) } - val redirectionCta = naviClickAction.copy(parameters = params) - NaviInsuranceDeeplinkNavigator.navigate( - activity, - ctaData = redirectionCta, - finish = redirectionCta.finish.orFalse(), - clearTask = redirectionCta.clearTask.orFalse() - ) } else -> { NaviInsuranceDeeplinkNavigator.navigate( diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/GenericComposableWidgetFactory.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/GenericComposableWidgetFactory.kt index 9633c846c5..54d6433116 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/GenericComposableWidgetFactory.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/GenericComposableWidgetFactory.kt @@ -94,7 +94,8 @@ fun GenericComposableWidgetFactory( widgetCallback: WidgetCallback? = null, isFirstItemVisible: Boolean = false, onWidgetUpdate: (updatedData: GenericWidgetDataInfo, isValid: Boolean) -> Unit = { _, _ -> }, - state: String = FooterButtonState.ENABLED.name + state: String = FooterButtonState.ENABLED.name, + showError: Boolean = false ) { LaunchedEffect(key1 = data.toString()) { data?.widgetAnalyticsProperties?.let { @@ -264,7 +265,8 @@ fun GenericComposableWidgetFactory( EditableTextWidgetComposable( data as? EditableTextWidgetData, widgetCallback, - onWidgetUpdate + onWidgetUpdate, + showError ) } WidgetTypes.SELECTABLE_TEXT_WIDGET.value -> { diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/EditableTextWidgetData.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/EditableTextWidgetData.kt index 525dd8455f..abb69ff9a9 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/EditableTextWidgetData.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/model/EditableTextWidgetData.kt @@ -30,6 +30,7 @@ data class EditableTextWidgetContentData( @SerializedName("title") val title: TextFieldData? = null, @SerializedName("fixedTitle") val fixedTitle: TextFieldData? = null, @SerializedName("textRules") val textRules: TextRules? = null, + @SerializedName("errorText") val errorText: TextFieldData? = null, @SerializedName("value") val value: String? = null, @SerializedName("id") val id: String? = null ) diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/EditableTextWidgetComposable.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/EditableTextWidgetComposable.kt index df732e9ac1..2c59bd5c52 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/EditableTextWidgetComposable.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/composewidget/widgets/EditableTextWidgetComposable.kt @@ -8,7 +8,7 @@ package com.navi.naviwidgets.composewidget.widgets import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.OutlinedTextField @@ -18,7 +18,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle @@ -30,6 +29,7 @@ import com.navi.design.theme.ttComposeFontFamily import com.navi.naviwidgets.callbacks.WidgetCallback import com.navi.naviwidgets.composewidget.model.EditableTextWidgetData import com.navi.naviwidgets.composewidget.reusable.colorBorderAlt +import com.navi.naviwidgets.composewidget.reusable.colorInputFields import com.navi.naviwidgets.extensions.NaviTextWidgetized import com.navi.naviwidgets.extensions.getKeyBoardType import com.navi.naviwidgets.extensions.hexToColor @@ -39,15 +39,13 @@ import com.navi.naviwidgets.extensions.setWidgetLayoutParams fun EditableTextWidgetComposable( editableTextData: EditableTextWidgetData?, widgetCallback: WidgetCallback? = null, - onWidgetUpdate: (updatedData: EditableTextWidgetData, isValid: Boolean) -> Unit + onWidgetUpdate: (updatedData: EditableTextWidgetData, isValid: Boolean) -> Unit, + isError: Boolean = false ) { var textLimit by remember { mutableStateOf(editableTextData?.widgetData?.title?.text ?: "") } editableTextData?.widgetData?.let { data -> setWidgetLayoutParams(widgetLayoutParams = editableTextData?.widgetLayoutParams) { - Row( - modifier = Modifier.background(Color.White).fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { + Column(modifier = Modifier.background(Color.White).fillMaxWidth()) { OutlinedTextField( modifier = Modifier.fillMaxWidth(), value = textLimit, @@ -73,6 +71,7 @@ fun EditableTextWidgetComposable( } } }, + isError = isError, placeholder = { NaviTextWidgetized(data?.hintText) }, leadingIcon = if (data?.fixedTitle != null) { @@ -85,13 +84,21 @@ fun EditableTextWidgetComposable( ), colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = colorBorderAlt, - unfocusedBorderColor = colorBorderAlt, + focusedBorderColor = if (isError) colorInputFields else colorBorderAlt, + unfocusedBorderColor = + if (isError) colorInputFields else colorBorderAlt, cursorColor = Color.Black, focusedContainerColor = Color.White, unfocusedContainerColor = Color.White, - errorCursorColor = Color.Black + errorCursorColor = Color.Black, + errorBorderColor = colorInputFields ), + supportingText = + if (isError) { + { NaviTextWidgetized(data.errorText) } + } else { + null + }, textStyle = TextStyle( fontFamily = ttComposeFontFamily,