diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/common/custom_view/CaptchaWidgetLayout.kt b/android/navi-insurance/src/main/java/com/navi/insurance/common/custom_view/CaptchaWidgetLayout.kt new file mode 100644 index 0000000000..40191fcf66 --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/common/custom_view/CaptchaWidgetLayout.kt @@ -0,0 +1,153 @@ +/* + * + * * Copyright © 2022-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.common.custom_view + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.widget.doOnTextChanged +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import com.navi.base.model.CtaData +import com.navi.base.model.CtaType +import com.navi.base.utils.log +import com.navi.base.utils.orZero +import com.navi.insurance.R +import com.navi.insurance.common.models.CaptchaWidgetData +import com.navi.insurance.common.models.NaviWidgetData +import com.navi.insurance.common.util.Base64Helper +import com.navi.insurance.common.widgets.FormBaseWidgetView +import com.navi.insurance.databinding.CaptchaWidgetLayoutBinding +import com.navi.insurance.models.request.FormTypedKeyData +import com.navi.naviwidgets.extensions.setTextFieldData +import com.navi.naviwidgets.models.WidgetLayoutParams +import com.navi.naviwidgets.utils.setWidgetLayoutParams +import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.WithFragmentBindings +import javax.inject.Inject + +@WithFragmentBindings +@AndroidEntryPoint +class CaptchaWidgetLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + FormBaseWidgetView(context, attrs) { + + @Inject lateinit var base64Helper: Base64Helper + private var binding: CaptchaWidgetLayoutBinding? = null + private var ctaCallback: ((cta: CtaData) -> Unit)? = null + private var widgetData: CaptchaWidgetData? = null + private var captchaText: String = "" + + override fun updateLayout( + binding: ViewDataBinding?, + naviWidgetData: NaviWidgetData, + callback: ((cta: CtaData) -> Unit)?, + fragment: Fragment?, + updateDataCallback: ((data: Any?) -> Unit)?, + ) { + if (naviWidgetData is CaptchaWidgetData && binding is CaptchaWidgetLayoutBinding) { + this.binding = binding + this.widgetData = naviWidgetData + this.ctaCallback = callback + + initViews(naviWidgetData, binding, callback) + } + } + + private fun initViews( + widgetData: CaptchaWidgetData, + binding: CaptchaWidgetLayoutBinding, + callback: ((cta: CtaData) -> Unit)?, + ) { + binding.captchaTitle.setTextFieldData(widgetData.title) + binding.refreshText.setTextFieldData(widgetData.refreshText) + + widgetData.captchaImage?.let { base64Image -> + try { + base64Helper.decodeBase64ToBitmap(base64Image, binding.captchaImage) + } catch (e: Exception) { + e.log() + } + } + + binding.refreshText.setOnClickListener { + widgetData.refreshCta?.let { cta -> callback?.invoke(cta) } + } + + if (widgetData.inputField?.captchaIncorrect == true) { + binding.errorText.apply { + setText(widgetData.inputField.incorrectErrorText) + visibility = VISIBLE + } + binding.captchaInput.apply { + setText(widgetData.inputField.incorrectInput) + captchaText = widgetData.inputField.incorrectInput ?: "" + setBackground( + AppCompatResources.getDrawable(context, R.drawable.rounded_red_border_rect) + ) + } + } + + widgetData.inputField?.let { inputField -> + binding.captchaInput.apply { + hint = inputField.hint + inputField.inputLength?.let { maxLength -> + filters = arrayOf(android.text.InputFilter.LengthFilter(maxLength)) + } + + doOnTextChanged { text, _, _, _ -> + val input = text?.toString()?.trim() ?: "" + if (input.matches(Regex(inputField.validationRegex ?: "[0-9]+"))) { + captchaText = input + binding.errorText.visibility = GONE + setBackground( + AppCompatResources.getDrawable( + context, + R.drawable.rounded_grey_border_rect, + ) + ) + } else { + binding.errorText.apply { + setText(inputField.validationErrorText) + visibility = VISIBLE + } + setBackground( + AppCompatResources.getDrawable( + context, + R.drawable.rounded_red_border_rect, + ) + ) + } + callback?.invoke(CtaData(type = CtaType.VALIDATE_FORM_PAGE.value)) + } + inputType = android.text.InputType.TYPE_CLASS_NUMBER + } + } + } + + override fun getWidgetData(): List? { + return widgetData?.inputField?.widgetKey?.let { key -> + listOf(FormTypedKeyData(attributes = mapOf(key to captchaText))) + } + } + + override fun isValidWidget(): Boolean { + return captchaText.isNotEmpty() && + (captchaText.length == widgetData?.inputField?.inputLength.orZero()) + } + + fun setLayoutParams(widgetLayoutParams: WidgetLayoutParams) { + binding?.root?.let { setWidgetLayoutParams(widgetLayoutParams, it) } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + binding = null + ctaCallback = null + widgetData = null + } +} diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/common/deserializer/NaviWidgetDeserializer.kt b/android/navi-insurance/src/main/java/com/navi/insurance/common/deserializer/NaviWidgetDeserializer.kt index 80213d8b6f..0434d9fad4 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/common/deserializer/NaviWidgetDeserializer.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/common/deserializer/NaviWidgetDeserializer.kt @@ -152,6 +152,7 @@ class NaviWidgetDeserializer : JsonDeserializer { NaviWidgetType.LOTTIE_WIDGET.value -> true NaviWidgetType.FOOTER_WITH_SLIDE_UP_ANIMATION.value -> true NaviWidgetType.FOOTER_WITH_TITLE_SUBTITLE.value -> true + NaviWidgetType.CAPTCHA_WIDGET.value -> true else -> false } } @@ -287,6 +288,7 @@ class NaviWidgetDeserializer : JsonDeserializer { NaviWidgetType.FOOTER_WITH_SLIDE_UP_ANIMATION -> FooterWithSlideUpAnimation::class.java NaviWidgetType.FOOTER_WITH_TITLE_SUBTITLE -> FooterWithTitleSubtileData::class.java + NaviWidgetType.CAPTCHA_WIDGET -> CaptchaWidgetData::class.java }, ) } diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/common/factory/FormViewHolderRegistry.kt b/android/navi-insurance/src/main/java/com/navi/insurance/common/factory/FormViewHolderRegistry.kt index 82aba79ec7..82e7b30423 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/common/factory/FormViewHolderRegistry.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/common/factory/FormViewHolderRegistry.kt @@ -81,6 +81,7 @@ class FormViewHolderRegistry { EXPANDABLE_QUESTIONS_WIDGET, CHECKBOX_WITH_GRID_OPTIONS_WIDGET, LOTTIE_WIDGET, + CAPTCHA_WIDGET, } fun setLottieHelper(lottieRemoteHelper: LottieRemoteHelper?) { @@ -1431,6 +1432,32 @@ class FormViewHolderRegistry { ) } } + FormWidgetType.CAPTCHA_WIDGET.name -> { + if (widget.widgetData is CaptchaWidgetData) { + viewGroup.addView( + CaptchaWidgetLayout(context).apply { + updateLayout( + binding = + DataBindingUtil.inflate( + LayoutInflater.from(context), + R.layout.captcha_widget_layout, + this, + true, + ), + callback = callback, + fragment = fragment, + naviWidgetData = widget.widgetData, + updateDataCallback = updateDataCallback, + ) + widget.widgetLayoutParams?.let { setLayoutParams(it) } + if (widget.maskSensitiveData == true) { + tag = SENSITIVE_VIEW + } + }, + index, + ) + } + } } } } diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/common/models/CaptchaWidgetData.kt b/android/navi-insurance/src/main/java/com/navi/insurance/common/models/CaptchaWidgetData.kt new file mode 100644 index 0000000000..75a20fa20c --- /dev/null +++ b/android/navi-insurance/src/main/java/com/navi/insurance/common/models/CaptchaWidgetData.kt @@ -0,0 +1,38 @@ +/* + * + * * Copyright © 2022-2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.insurance.common.models + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import com.navi.base.model.CtaData +import com.navi.naviwidgets.models.response.TextFieldData +import kotlinx.parcelize.Parcelize + +data class CaptchaWidgetData( + val title: TextFieldData? = null, + val captchaImage: String? = null, + val refreshText: TextFieldData? = null, + val refreshCta: CtaData? = null, + val inputField: TextInputField? = null, +) : NaviWidgetData() { + override var widgetNameForBaseAdapter: String? = NaviWidgetType.CAPTCHA_WIDGET.name +} + +@Parcelize +data class TextInputField( + @SerializedName("inputLength") val inputLength: Int? = null, + @SerializedName("title") val title: TextFieldData? = null, + @SerializedName("hint") val hint: String? = null, + @SerializedName("validationRegex") val validationRegex: String? = null, + @SerializedName("validationErrorText") val validationErrorText: String? = null, + @SerializedName("incorrectErrorText") val incorrectErrorText: String? = null, + @SerializedName("incorrectInput") val incorrectInput: String? = null, + @SerializedName("captchaIncorrect") val captchaIncorrect: Boolean? = null, + @SerializedName("widgetKey") val widgetKey: String? = null, + @SerializedName("disabled") val disabled: Boolean? = null, +) : Parcelable diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/common/models/NaviWidgetData.kt b/android/navi-insurance/src/main/java/com/navi/insurance/common/models/NaviWidgetData.kt index a37097b65a..e47823212a 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/common/models/NaviWidgetData.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/common/models/NaviWidgetData.kt @@ -96,10 +96,11 @@ enum class NaviWidgetType(val value: String) { SECTION_ITEMS_WITH_SLIDE_SELECTOR_WIDGET("SECTION_ITEMS_WITH_SLIDE_SELECTOR_WIDGET"), SECTION_WITH_ITEMS_WIDGET_V2("SECTION_WITH_ITEMS_WIDGET_V2"), CHECKBOX_WITH_GRID_OPTIONS_WIDGET("CHECKBOX_WITH_GRID_OPTIONS_WIDGET"), + LOTTIE_WIDGET("LOTTIE_WIDGET"), + CAPTCHA_WIDGET("CAPTCHA_WIDGET"), FEEDBACK_FORM_WIDGET("FEEDBACK_FORM_WIDGET"), EXPANDABLE_QUESTIONS_WIDGET("EXPANDABLE_QUESTIONS_WIDGET"), TAB_LAYOUT_WITH_NUMBER_PICKER_WIDGET("TAB_LAYOUT_WITH_NUMBER_PICKER_WIDGET"), - LOTTIE_WIDGET("LOTTIE_WIDGET"), FOOTER_WITH_SLIDE_UP_ANIMATION("FOOTER_WITH_SLIDE_UP_ANIMATION"), FOOTER_WITH_TITLE_SUBTITLE("FOOTER_WITH_TITLE_SUBTITLE"), } diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/formbase/post_purchase/PostPurchaseFormBasedFragment.kt b/android/navi-insurance/src/main/java/com/navi/insurance/formbase/post_purchase/PostPurchaseFormBasedFragment.kt index 00c51a38f5..1a7882ab9d 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/formbase/post_purchase/PostPurchaseFormBasedFragment.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/formbase/post_purchase/PostPurchaseFormBasedFragment.kt @@ -481,6 +481,16 @@ class PostPurchaseFormBasedFragment : GiBaseFragment(), ActionHandler.ActionOwne ctaTitle = btnCta?.title, ) } + CtaType.RELOAD_SCREEN.value -> { + viewLifecycleOwner.lifecycleScope.launch { + viewModel.fetchFormPageResponse( + referenceId ?: viewModel.preKycPage, + journeyKey, + requestPageType, + applicationId, + ) + } + } else -> { val params = cta.parameters?.toMutableList() if (params?.contains(LineItem(key = ASSET_DETAILS_EXTRA)) == false) { diff --git a/android/navi-insurance/src/main/java/com/navi/insurance/formbase/post_purchase/vm/PostPurchaseFormFragmentVM.kt b/android/navi-insurance/src/main/java/com/navi/insurance/formbase/post_purchase/vm/PostPurchaseFormFragmentVM.kt index 60a8726550..6ca7a936b2 100644 --- a/android/navi-insurance/src/main/java/com/navi/insurance/formbase/post_purchase/vm/PostPurchaseFormFragmentVM.kt +++ b/android/navi-insurance/src/main/java/com/navi/insurance/formbase/post_purchase/vm/PostPurchaseFormFragmentVM.kt @@ -99,7 +99,7 @@ constructor( private val GI_REQUEST_CODE = 113 private val keyQuoteId = "quoteId" private val keyPageType = "pageType" - private val preKycPage = "pre_kyc_page" + val preKycPage = "pre_kyc_page" suspend fun fetchFormPageResponse( referenceId: String?, diff --git a/android/navi-insurance/src/main/res/layout/captcha_widget_layout.xml b/android/navi-insurance/src/main/res/layout/captcha_widget_layout.xml new file mode 100644 index 0000000000..be9086108f --- /dev/null +++ b/android/navi-insurance/src/main/res/layout/captcha_widget_layout.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + diff --git a/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/WidgetExt.kt b/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/WidgetExt.kt index 9ceb734dc2..265cc53be0 100644 --- a/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/WidgetExt.kt +++ b/android/navi-widgets/src/main/java/com/navi/naviwidgets/extensions/WidgetExt.kt @@ -418,7 +418,8 @@ fun TextView.setTextFieldData( null } if (!textDrawableData.left?.url.isNullOrEmpty() && leftDrawable == null) { - textDrawableData.left?.url?.let { + drawablePadding = textDrawableData.left.drawablePadding + textDrawableData.left.url?.let { loadDrawableFromUrl( context, it, @@ -434,7 +435,8 @@ fun TextView.setTextFieldData( } } } else if (!textDrawableData.right?.url.isNullOrEmpty() && rightDrawable == null) { - textDrawableData.right?.url?.let { + drawablePadding = textDrawableData.right.drawablePadding + textDrawableData.right.url?.let { loadDrawableFromUrl( context, it, @@ -450,7 +452,8 @@ fun TextView.setTextFieldData( } } } else if (!textDrawableData.top?.url.isNullOrEmpty() && topDrawable == null) { - textDrawableData.top?.url?.let { + drawablePadding = textDrawableData.top.drawablePadding + textDrawableData.top.url?.let { loadDrawableFromUrl( context, it, @@ -466,7 +469,8 @@ fun TextView.setTextFieldData( } } } else if (!textDrawableData.bottom?.url.isNullOrEmpty() && bottomDrawable == null) { - textDrawableData.bottom?.url?.let { + drawablePadding = textDrawableData.bottom.drawablePadding + textDrawableData.bottom.url?.let { loadDrawableFromUrl( context, it,