NTP-58342 | Captcha Widget (#16219)
This commit is contained in:
@@ -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<FormTypedKeyData>? {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -152,6 +152,7 @@ class NaviWidgetDeserializer : JsonDeserializer<NaviWidget> {
|
||||
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<NaviWidget> {
|
||||
NaviWidgetType.FOOTER_WITH_SLIDE_UP_ANIMATION ->
|
||||
FooterWithSlideUpAnimation::class.java
|
||||
NaviWidgetType.FOOTER_WITH_TITLE_SUBTITLE -> FooterWithTitleSubtileData::class.java
|
||||
NaviWidgetType.CAPTCHA_WIDGET -> CaptchaWidgetData::class.java
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<CaptchaWidgetLayoutBinding>(
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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"),
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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?,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/captcha_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/primaryColor"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Enter captcha" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/refresh_text"
|
||||
style="@style/TextV8Style"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refresh"
|
||||
android:drawableEnd="@drawable/refresh"
|
||||
android:drawablePadding="2dp"
|
||||
android:drawableTint="@color/ctaPurplePrimaryColor"
|
||||
android:background="@android:color/transparent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/captcha_title" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/captcha_input"
|
||||
style="@style/TextInputV2Style"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/rounded_grey_border_rect"
|
||||
android:gravity="center_vertical"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:paddingHorizontal="@dimen/dp_16"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/captcha_image"
|
||||
app:layout_constraintTop_toBottomOf="@id/captcha_title"
|
||||
tools:hint="Enter captcha here" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/captcha_image"
|
||||
android:layout_width="144dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/rounded_grey_border_rect"
|
||||
android:padding="4dp"
|
||||
android:scaleType="fitXY"
|
||||
app:layout_constraintStart_toEndOf="@id/captcha_input"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/captcha_input" />
|
||||
|
||||
<com.navi.design.textview.NaviTextView
|
||||
android:id="@+id/error_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:drawableStart="@drawable/ic_alert_error_red"
|
||||
android:drawablePadding="@dimen/dp_5"
|
||||
android:drawableTint="@color/color_EF0000"
|
||||
android:fontFamily="@font/navi_body_regular"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/color_EF0000"
|
||||
android:textSize="@dimen/sp_12"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/captcha_input"
|
||||
tools:text="Invalid captcha"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user