NTP-31 | Regression bugs of investment-tab (#12484)

Co-authored-by: saksham <saksham>
This commit is contained in:
Saksham Mahajan
2024-09-12 23:11:17 +05:30
committed by GitHub
parent b95543a45e
commit 46bbb68629
10 changed files with 227 additions and 36 deletions

View File

@@ -20,6 +20,7 @@ import com.navi.common.utils.Constants.HOPPER_PROCESS_ON_COMPLETE
import com.navi.common.utils.Constants.HOPPER_PROCESS_ON_EXECUTE
import com.navi.common.utils.Constants.HOPPER_PROCESS_ON_START
import com.navi.common.utils.Constants.HOPPER_PROCESS_UN_BLOCK_SCREEN
import com.navi.common.utils.Constants.HOPPER_THRESHOLD_FOR_NEW_JOB
import com.navi.common.utils.Constants.STATUS
import com.naviapp.home.common.hopperProcessor.HopperProcessHandler
import javax.inject.Inject
@@ -35,6 +36,7 @@ class HopperProcessor @Inject constructor(private val viewModelMapper: ViewModel
private lateinit var activity: ComponentActivity
private lateinit var onResult: (CtaData?) -> Unit
private var job: Job? = null
private var processorLastCancelationTime: Long = 0
fun start(activity: ComponentActivity, ctaData: CtaData, onResult: (CtaData?) -> Unit) {
this.activity = activity
@@ -42,7 +44,27 @@ class HopperProcessor @Inject constructor(private val viewModelMapper: ViewModel
this.onResult = onResult
job?.cancel()
if (canStartNewJob().not()) {
onComplete(false)
} else {
startNewJob()
}
}
fun cancel(activity: ComponentActivity) {
processorLastCancelationTime = System.currentTimeMillis()
unBlockScreen()
NaviTrackEvent.trackEvent(eventName = HOPPER_PROCESS_JOB_CANCELLED_CALLED)
this.activity = activity
if (job?.isActive.orFalse()) {
NaviTrackEvent.trackEvent(eventName = HOPPER_PROCESS_JOB_CANCELLED_SUCCESSFULLY)
job?.cancel()
onComplete(false)
}
}
private fun startNewJob() {
processorLastCancelationTime = 0
job = Job()
job?.let { job ->
job.invokeOnCompletion { throwable ->
@@ -55,16 +77,6 @@ class HopperProcessor @Inject constructor(private val viewModelMapper: ViewModel
}
}
fun cancel(activity: ComponentActivity) {
NaviTrackEvent.trackEvent(eventName = HOPPER_PROCESS_JOB_CANCELLED_CALLED)
this.activity = activity
if (job?.isActive.orFalse()) {
NaviTrackEvent.trackEvent(eventName = HOPPER_PROCESS_JOB_CANCELLED_SUCCESSFULLY)
job?.cancel()
onComplete(false)
}
}
override fun onStart() {
NaviTrackEvent.trackEvent(eventName = HOPPER_PROCESS_ON_START)
blockScreen()
@@ -105,4 +117,9 @@ class HopperProcessor @Inject constructor(private val viewModelMapper: ViewModel
NaviTrackEvent.trackEvent(eventName = HOPPER_PROCESS_UN_BLOCK_SCREEN)
runOnUiThread { (activity as? BaseActivity)?.unblockInteraction() }
}
private fun canStartNewJob(): Boolean {
return (System.currentTimeMillis() - processorLastCancelationTime >
HOPPER_THRESHOLD_FOR_NEW_JOB)
}
}

View File

@@ -36,7 +36,7 @@ import com.navi.common.utils.Constants.HOPPER
import com.navi.common.utils.toActionData
import com.navi.common.utils.toCtaData
import com.navi.design.utils.clickableWithNoGesture
import com.navi.naviwidgets.composewidget.reusable.FooterButtonComposable
import com.navi.naviwidgets.composewidget.reusable.ButtonComposable
import com.navi.naviwidgets.extensions.NaviImage
import com.navi.naviwidgets.extensions.NaviTextWidgetized
import com.navi.naviwidgets.models.FooterButtonData
@@ -227,7 +227,7 @@ fun ActionCardComposable(
)
)
FooterButtonComposable(
ButtonComposable(
data =
FooterButtonData(
title = data.buttonText,

View File

@@ -0,0 +1,136 @@
/*
*
* * Copyright © 2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.naviwidgets.composewidget.reusable
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.RippleTheme
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import com.navi.base.model.CtaData
import com.navi.naviwidgets.callbacks.WidgetCallback
import com.navi.naviwidgets.extensions.NaviTextWidgetized
import com.navi.naviwidgets.extensions.getColorFromHexCode
import com.navi.naviwidgets.models.FooterButtonData
import com.navi.naviwidgets.models.FooterButtonState
import com.naviapp.utils.Constants.LOTTIE_LOADER_URL
private object NoRippleTheme : RippleTheme {
@Composable override fun defaultColor() = Color.Unspecified
@Composable
override fun rippleAlpha() =
RippleAlpha(
pressedAlpha = 0.0f,
focusedAlpha = 0.0f,
draggedAlpha = 0.0f,
hoveredAlpha = 0.0f
)
}
@Composable
fun ButtonComposable(
data: FooterButtonData? = null,
textModifier: Modifier? = null,
state: String? = FooterButtonState.ENABLED.name,
widgetCallback: WidgetCallback? = null,
modifier: Modifier? = null,
contentPadding: PaddingValues = PaddingValues(horizontal = 12.dp, vertical = 8.dp),
onClick: (ctaData: CtaData?) -> Unit = {}
) {
val buttonBgColor = data?.backgroundColor?.let { getColorFromHexCode(hex = it) } ?: Color.White
var buttonWidth by remember { mutableStateOf(0.dp) }
var buttonHeight by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
CompositionLocalProvider(LocalRippleTheme provides NoRippleTheme) {
var lastClickTime by remember { mutableStateOf(0L) }
Button(
onClick = {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime >= 600) {
lastClickTime = currentTime
if (
state == FooterButtonState.ENABLED.name ||
state == FooterButtonState.SNACKBAR.name
) {
widgetCallback?.let { data?.cta?.let { widgetCallback?.onClick(it) } }
?: run { onClick.invoke(data?.cta) }
}
}
},
enabled = getFooterButtonState(state = state),
colors =
ButtonDefaults.buttonColors(
disabledContainerColor = buttonBgColor.copy(alpha = 0.56f),
containerColor = buttonBgColor,
),
modifier =
modifier
?: Modifier.wrapContentWidth()
.wrapContentHeight()
.padding(horizontal = 16.dp)
.padding(top = 16.dp, bottom = 32.dp)
.heightIn(min = 16.dp),
shape = RoundedCornerShape(4.dp),
contentPadding = contentPadding
) {
if (state == FooterButtonState.LOADING.name) {
val spec = LottieCompositionSpec.Url(url = LOTTIE_LOADER_URL)
val composition by rememberLottieComposition(spec)
LottieAnimation(
composition = composition,
iterations = LottieConstants.IterateForever,
modifier = Modifier.width(buttonWidth).height(buttonHeight)
)
} else {
NaviTextWidgetized(
textFieldData = data?.title,
modifier =
textModifier?.onGloballyPositioned { coordinates ->
buttonWidth = with(density) { coordinates.size.width.toDp() }
buttonHeight = with(density) { coordinates.size.height.toDp() }
} ?: Modifier.wrapContentWidth().wrapContentHeight()
)
}
}
}
}
private fun getFooterButtonState(state: String?): Boolean {
return when (state) {
FooterButtonState.ENABLED.name -> true
FooterButtonState.DISABLED.name -> false
FooterButtonState.LOADING.name -> true
FooterButtonState.ERROR.name -> false
FooterButtonState.SNACKBAR.name -> true
else -> true
}
}

View File

@@ -483,6 +483,8 @@ object Constants {
const val KYC_JOURNEY = "KYC_JOURNEY"
const val SPACER_DEFAULT = 16
const val STATUS_BAR_COLOR = "statusBarColor"
const val LOTTIE_LOADER_URL =
"https://public-assets.prod.navi-sa.in/home_uitron/cta_loader.json"
// Constants for Animation label
object AnimationLabels {

View File

@@ -474,8 +474,16 @@ class CheckerActivity : BasePaymentActivity() {
lifecycleScope.launch {
delay(exitTime.toLong())
if (resultCode == RESULT_OK) {
sendEvent(
eventName = HPC_PAN_REDIRECTION_PAGE_EXIT,
extraAttributes = hashMapOf(STATUS to SUCCESS)
)
viewModel.postPanDataV2()
} else {
sendEvent(
eventName = HPC_PAN_REDIRECTION_PAGE_EXIT,
extraAttributes = hashMapOf(STATUS to FAILURE)
)
finish()
}
}
@@ -486,8 +494,16 @@ class CheckerActivity : BasePaymentActivity() {
lifecycleScope.launch {
delay(exitTime.toLong())
if (resultCode == RESULT_OK) {
sendEvent(
eventName = HPC_PAN_REDIRECTION_PAGE_EXIT,
extraAttributes = hashMapOf(STATUS to SUCCESS)
)
viewModel.postNameDataV2()
} else {
sendEvent(
eventName = HPC_PAN_REDIRECTION_PAGE_EXIT,
extraAttributes = hashMapOf(STATUS to FAILURE)
)
finish()
}
}
@@ -526,8 +542,10 @@ class CheckerActivity : BasePaymentActivity() {
private fun needActivityResultCode(): Int? {
return if (type == HPC_PAN_REDIRECTION_PAGE) {
sendEvent(HPC_PAN_REDIRECTION_PAGE_ENTRY)
ActivityRequestCode.HPC_PAN_REDIRECTION_REQUEST_CODE.code
} else if (type == HPC_NAME_REDIRECTION_PAGE) {
sendEvent(HPC_PAN_REDIRECTION_PAGE_ENTRY)
ActivityRequestCode.HPC_NAME_REDIRECTION_REQUEST_CODE.code
} else null
}
@@ -905,6 +923,8 @@ class CheckerActivity : BasePaymentActivity() {
const val KYC_CALLBACK = "KYC_CALLBACK"
const val CVL_KYC_CHECK = "CVL_KYC_CHECK"
const val HPC_PAN_REDIRECTION_PAGE = "HPC_PAN_REDIRECTION_PAGE"
const val HPC_PAN_REDIRECTION_PAGE_ENTRY = "amc_one_profile_initiated"
const val HPC_PAN_REDIRECTION_PAGE_EXIT = "amc_one_profile_exit"
const val HPC_NAME_REDIRECTION_PAGE = "HPC_NAME_REDIRECTION_PAGE"
const val PL_DISBURSED_JOURNEY = "pl_disbursed_journey"
const val E_SIGN = "E_SIGN_START"
@@ -921,5 +941,8 @@ class CheckerActivity : BasePaymentActivity() {
const val DATA = "DATA"
const val TYPE = "TYPE"
const val ERROR_TAG = "ERROR_TAG"
const val STATUS = "status"
const val SUCCESS = "success"
const val FAILURE = "failure"
}
}

View File

@@ -35,6 +35,8 @@ import com.navi.amc.fundbuy.models.PaymentPostData
import com.navi.amc.fundbuy.models.SipDetailsData
import com.navi.amc.fundbuy.viewmodel.FundBuyV2ViewModel
import com.navi.amc.utils.AmcAnalytics
import com.navi.amc.utils.AmcAnalytics.AMC_AMOUNT_PAGE_LUMPSUM_TAB_CLICKED
import com.navi.amc.utils.AmcAnalytics.AMC_AMOUNT_PAGE_SIP_TAB_CLICKED
import com.navi.amc.utils.Constant
import com.navi.amc.utils.Constant.ADD_AMOUNT
import com.navi.amc.utils.Constant.AMC
@@ -174,10 +176,20 @@ class FundBuyingFragmentV2 : AmcBaseFragment(), WidgetCallback {
}
binding.switchView.setProperties(left, right, defaultLeftChecked) { onLeftOptionSelected ->
val isSipSelectedBefore = viewModel.isSipSelected.orFalse()
viewModel.isSipSelected =
if (onLeftOptionSelected) {
left == Constant.SIP
} else right == Constant.SIP
if (isSipSelectedBefore != viewModel.isSipSelected.orFalse()) {
if (viewModel.isSipSelected.orFalse()) {
sendEvent(AMC_AMOUNT_PAGE_SIP_TAB_CLICKED)
} else {
sendEvent(AMC_AMOUNT_PAGE_LUMPSUM_TAB_CLICKED)
}
}
if (viewModel.isSipSelected.orFalse()) {
sipSelected()
} else {

View File

@@ -28,7 +28,9 @@ import com.navi.amc.fundbuy.models.SipTypeScreenContent
import com.navi.amc.fundbuy.models.SipTypeScreenData
import com.navi.amc.fundbuy.models.SipTypeScreenState
import com.navi.amc.fundbuy.viewmodel.SipTypeViewModel
import com.navi.amc.utils.AmcAnalytics.AMC_SIP_CHOOSE_TYPE_CLICKED
import com.navi.amc.utils.AmcAnalytics.ISIN
import com.navi.amc.utils.AmcAnalytics.SELECTED_TYPE
import com.navi.amc.utils.AmcAnalytics.SIP_TYPE
import com.navi.amc.utils.Constant
import com.navi.amc.utils.Constant.AMOUNT
@@ -133,6 +135,12 @@ class SipTypeFragment() : AmcBaseFragment() {
}
private fun clickAction(item: SipOption) {
if (viewModel.selectedType != item.id.orEmpty()) {
sendEvent(
eventName = AMC_SIP_CHOOSE_TYPE_CLICKED,
extraAttributes = hashMapOf(SELECTED_TYPE to item.id.orEmpty())
)
}
viewModel.selectedType = item.id.orEmpty()
viewModel.nextPageCta = item.actionData
setFooterData()

View File

@@ -266,6 +266,10 @@ object AmcAnalytics {
const val LIMIT = "limit"
const val AMC_INIT_AUTOPAY_SETUP_SCREEN = "amc_init_autopay_setup_screen"
const val AMC_AUTOPAY_MODE_CHANGED = "amc_autopay_mode_changed"
const val AMC_AMOUNT_PAGE_SIP_TAB_CLICKED = "amc_amount_page_sip_tab_clicked"
const val AMC_AMOUNT_PAGE_LUMPSUM_TAB_CLICKED = "amc_amount_page_lumpsum_tab_clicked"
const val AMC_SIP_CHOOSE_TYPE_CLICKED = "amc_sip_choose_type_clicked"
const val SELECTED_TYPE = "selected_type"
const val AMC_OTP_VERIFY_SUCCESSFUL = "amc_otp_verify_successful"
const val AMC_OTP_VERIFY_SUCCESSFUL_RESPONSE_DATA = "amc_otp_verify_response_data"

View File

@@ -280,6 +280,7 @@ object Constants {
/*Hooper CONSTANTS*/
const val HOPPER = "HOPPER"
const val HOPPER_THRESHOLD_FOR_NEW_JOB = 2000
const val HOPPER_PROCESS_ON_START = "hopper_process_on_start"
const val HOPPER_PROCESS_ON_EXECUTE = "hopper_process_on_execute"
const val HOPPER_PROCESS_JOB_CANCELLED_CALLED = "hopper_process_job_cancelled_called"

View File

@@ -8,12 +8,12 @@
package com.navi.naviwidgets.composewidget.reusable
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.material.ripple.RippleAlpha
@@ -28,14 +28,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import com.navi.base.model.CtaData
import com.navi.naviwidgets.callbacks.WidgetCallback
import com.navi.naviwidgets.extensions.NaviTextWidgetized
import com.navi.naviwidgets.extensions.getColorFromHexCode
@@ -58,17 +55,12 @@ private object NoRippleTheme : RippleTheme {
@Composable
fun FooterButtonComposable(
data: FooterButtonData? = null,
textModifier: Modifier? = null,
textModifier: Modifier? = Modifier,
state: String? = FooterButtonState.ENABLED.name,
widgetCallback: WidgetCallback? = null,
modifier: Modifier? = null,
contentPadding: PaddingValues = PaddingValues(horizontal = 12.dp, vertical = 8.dp),
onClick: (ctaData: CtaData?) -> Unit = {}
modifier: Modifier? = null
) {
val buttonBgColor = data?.backgroundColor?.let { getColorFromHexCode(hex = it) } ?: Color.White
var buttonWidth by remember { mutableStateOf(0.dp) }
var buttonHeight by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
CompositionLocalProvider(LocalRippleTheme provides NoRippleTheme) {
var lastClickTime by remember { mutableStateOf(0L) }
Button(
@@ -79,10 +71,8 @@ fun FooterButtonComposable(
if (
state == FooterButtonState.ENABLED.name ||
state == FooterButtonState.SNACKBAR.name
) {
widgetCallback?.let { data?.cta?.let { widgetCallback?.onClick(it) } }
?: run { onClick.invoke(data?.cta) }
}
)
data?.cta?.let { widgetCallback?.onClick(it) }
}
},
enabled = getFooterButtonState(state = state),
@@ -92,14 +82,14 @@ fun FooterButtonComposable(
containerColor = buttonBgColor,
),
modifier =
modifier
?: Modifier.wrapContentWidth()
modifier?.fillMaxWidth()?.wrapContentHeight()?.heightIn(min = 48.dp)
?: Modifier.fillMaxWidth()
.wrapContentHeight()
.padding(horizontal = 16.dp)
.padding(top = 16.dp, bottom = 32.dp)
.heightIn(min = 16.dp),
.heightIn(min = 48.dp),
shape = RoundedCornerShape(4.dp),
contentPadding = contentPadding
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp)
) {
if (state == FooterButtonState.LOADING.name) {
val spec =
@@ -110,16 +100,14 @@ fun FooterButtonComposable(
LottieAnimation(
composition = composition,
iterations = LottieConstants.IterateForever,
modifier = Modifier.width(buttonWidth).height(buttonHeight)
modifier = Modifier.width(32.dp).height(20.dp)
)
} else {
NaviTextWidgetized(
textFieldData = data?.title,
modifier =
textModifier?.onGloballyPositioned { coordinates ->
buttonWidth = with(density) { coordinates.size.width.toDp() }
buttonHeight = with(density) { coordinates.size.height.toDp() }
} ?: Modifier.wrapContentWidth().wrapContentHeight()
textModifier?.fillMaxWidth()?.wrapContentHeight()
?: Modifier.fillMaxWidth().wrapContentHeight()
)
}
}