NTP-38969 | Lite USP Revamp (#15245)

Co-authored-by: Akshita Singh <akshita.singh@navi.com>
This commit is contained in:
Aditya Narayan Malik
2025-03-05 15:41:55 +05:30
committed by GitHub
parent 3e03ea40d8
commit 70718d769f
9 changed files with 282 additions and 10 deletions

View File

@@ -9,11 +9,14 @@ package com.navi.common.lottie
object LottieRepository {
const val ARC_EXPLANATORY_LOTTIE_URL = "ARC_EXPLANATORY_LOTTIE_URL"
const val LITE_MASTHEAD_LOTTIE_URL = "LITE_MASTHEAD_LOTTIE_URL"
private val lottieUrlMap =
mapOf(
ARC_EXPLANATORY_LOTTIE_URL to
"https://public-assets.prod.navi-pay.in/lotties/arc_explanation.lottie"
"https://public-assets.prod.navi-pay.in/lotties/arc_explanation.lottie",
LITE_MASTHEAD_LOTTIE_URL to
"https://public-assets.prod.navi-sa.in/navi-pay/lottie/navi-pay-lite-masthead.lottie",
)
fun getLottieUrl(lottieName: String): String {

View File

@@ -18,6 +18,7 @@ object NaviPayColor {
val brandSecondaryGreen = Color(0xFF22D081)
val brandSupportingMidGreen = Color(0xFFAFECCD)
val brandSupportingLightGreen = Color(0xFFE4FFED)
val liteNonOnboardedHeaderBgGreenColor = Color(0xFFFAFFFC)
val brandPrimaryPurple = Color(0xFF3C0050)
val inputFieldDefault = Color(0xFFA8A8A8)
val inputFieldFilled = Color(0xFF191919)

View File

@@ -0,0 +1,15 @@
/*
*
* * Copyright © 2025 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.management.lite.models.view
enum class UpiLiteUspExperimentVariant {
DEFAULT,
FAST_TRANSACTION,
BANK_SERVER_DOWN,
SIMPLIFY_BANK_STATEMENTS,
}

View File

@@ -49,7 +49,9 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
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.draw.alpha
@@ -60,6 +62,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -67,6 +70,7 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
@@ -76,13 +80,17 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.airbnb.lottie.compose.LottieConstants
import com.navi.common.R as CommonR
import com.navi.common.lottie.LottieRepository
import com.navi.common.model.NaviLottieCompositionSpecType
import com.navi.common.utils.CommonUtils.getDisplayableAmount
import com.navi.common.utils.CurrencyMaskTransformation
import com.navi.design.font.FontWeightEnum
import com.navi.design.font.getFontWeight
import com.navi.design.font.naviFontFamily
import com.navi.design.snackbar.SuccessSnackBar
import com.navi.naviwidgets.R as WidgetsR
import com.navi.naviwidgets.extensions.NaviText
import com.navi.pay.R
import com.navi.pay.analytics.NaviPayAnalytics
@@ -100,6 +108,7 @@ import com.navi.pay.common.utils.SnackBarPredefinedConfig
import com.navi.pay.management.common.sendmoney.model.view.BankAccountsState
import com.navi.pay.management.lite.models.NaviPayUpiLiteConfig
import com.navi.pay.management.lite.models.view.AmountChipEntity
import com.navi.pay.management.lite.models.view.UpiLiteUspExperimentVariant
import com.navi.pay.management.lite.util.AddBalanceButtonSource
import com.navi.pay.management.lite.util.SetPinButtonSource
import com.navi.pay.management.lite.util.UpiLiteMainCtaState
@@ -191,6 +200,8 @@ fun UpiLiteSection(
upiLiteViewModel.maximumTopUpAmountAllowed.collectAsStateWithLifecycle()
val naviPayUpiLiteConfig by upiLiteViewModel.naviPayUpiLiteConfig.collectAsStateWithLifecycle()
val isMainCtaEnabled by upiLiteViewModel.isMainCtaEnabled.collectAsStateWithLifecycle()
val upiLiteUspExperimentVariant by
upiLiteViewModel.upiLiteUspExperimentVariant.collectAsStateWithLifecycle()
val scrollState = rememberScrollState()
val linkedAccountWithActiveLiteAccount by
@@ -214,7 +225,7 @@ fun UpiLiteSection(
Scaffold(
modifier = modifier,
content = {
Box(modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min)) {
Box(modifier = Modifier.fillMaxWidth()) {
Column(
modifier =
Modifier.fillMaxWidth()
@@ -231,6 +242,7 @@ fun UpiLiteSection(
onMenuOptionClicked = onMenuOptionClicked,
isLiteCLSyncRequired = isLiteCLSyncRequired,
naviPayUpiLiteConfig = naviPayUpiLiteConfig,
upiLiteUspExperimentVariant = upiLiteUspExperimentVariant,
)
Spacer(modifier = Modifier.height(32.dp))
@@ -649,14 +661,24 @@ fun UpiLiteBannerSection(
onMenuOptionClicked: () -> Unit,
isLiteCLSyncRequired: Boolean,
naviPayUpiLiteConfig: NaviPayUpiLiteConfig,
upiLiteUspExperimentVariant: UpiLiteUspExperimentVariant,
) {
if (!isUserOnboarded) {
UpiLiteNonOnboardedBanner(
onHelpCtaClicked = onHelpCtaClicked,
onBackClick = onBackClick,
onMenuOptionClicked = onMenuOptionClicked,
naviPayUpiLiteConfig = naviPayUpiLiteConfig,
)
if (upiLiteUspExperimentVariant == UpiLiteUspExperimentVariant.DEFAULT) {
UpiLiteNonOnboardedMultipleUspBanner(
onHelpCtaClicked = onHelpCtaClicked,
onBackClick = onBackClick,
onMenuOptionClicked = onMenuOptionClicked,
naviPayUpiLiteConfig = naviPayUpiLiteConfig,
)
} else {
UpiLiteNonOnboardedSingleUspBanner(
onHelpCtaClicked = onHelpCtaClicked,
onBackClick = onBackClick,
onMenuOptionClicked = onMenuOptionClicked,
upiLiteExperimentVariant = upiLiteUspExperimentVariant,
)
}
} else if (isLiteBalanceLow) {
UpiLiteLowBalanceBanner(
onHelpCtaClicked = onHelpCtaClicked,
@@ -813,7 +835,7 @@ private fun UpiLiteBalanceSection(upiLiteBalance: String, titleText: String, tex
}
@Composable
fun UpiLiteNonOnboardedBanner(
fun UpiLiteNonOnboardedMultipleUspBanner(
onHelpCtaClicked: () -> Unit,
onBackClick: () -> Unit,
onMenuOptionClicked: () -> Unit,
@@ -893,6 +915,168 @@ fun VerticalBar() {
Image(painter = painterResource(id = R.drawable.ic_np_bar), contentDescription = "")
}
@Composable
fun UpiLiteNonOnboardedSingleUspBanner(
onHelpCtaClicked: () -> Unit,
onBackClick: () -> Unit,
onMenuOptionClicked: () -> Unit,
upiLiteExperimentVariant: UpiLiteUspExperimentVariant,
) {
Column(
modifier =
Modifier.fillMaxWidth()
.background(color = NaviPayColor.liteNonOnboardedHeaderBgGreenColor)
) {
BannerHeaderSection(
onHelpCtaClicked = onHelpCtaClicked,
shouldShowThreeDotMenu = false,
onBackClick = onBackClick,
onMenuOptionClicked = onMenuOptionClicked,
)
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Column {
when (upiLiteExperimentVariant) {
UpiLiteUspExperimentVariant.FAST_TRANSACTION -> FastPaymentsUspText()
UpiLiteUspExperimentVariant.BANK_SERVER_DOWN -> BankServerDownText()
UpiLiteUspExperimentVariant.SIMPLIFY_BANK_STATEMENTS -> BankStatementFullText()
else -> {
FastPaymentsUspText()
}
}
}
Spacer(modifier = Modifier.height(8.dp))
LiteUspWidgetSection()
}
}
}
@Composable
fun LiteUspWidgetSection() {
var isLottieLoaded by remember { mutableStateOf(false) }
Box {
Image(
painter = painterResource(WidgetsR.drawable.image_transparent_placeholder_small),
contentDescription = null,
modifier =
Modifier.background(color = NaviPayColor.liteNonOnboardedHeaderBgGreenColor)
.alpha(if (isLottieLoaded) 0f else 1f)
.size(120.dp),
contentScale = ContentScale.FillWidth,
)
NaviPayLottieAnimation(
modifier = Modifier,
lottieFileName =
LottieRepository.getLottieUrl(LottieRepository.LITE_MASTHEAD_LOTTIE_URL),
lottieCompositionSpecType = NaviLottieCompositionSpecType.Url,
iterations = LottieConstants.IterateForever,
showLottieInfiniteTimes = true,
onAnimationStart = { isLottieLoaded = true },
contentScale = ContentScale.FillWidth,
)
}
}
@Composable
fun FastPaymentsUspText() {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = R.drawable.ic_lite_super_fast_payment_logo),
contentDescription = null,
modifier = Modifier,
)
NaviText(
text = stringResource(id = R.string.np_upi_lite_masthead_fast_payments_title),
fontSize = 18.sp,
lineHeight = 24.sp,
fontFamily = naviFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
color = NaviPayColor.textPrimary,
fontStyle = FontStyle.Italic,
)
}
NaviText(
text = stringResource(id = R.string.np_upi_lite_masthead_fast_payments_sub_title),
fontSize = 18.sp,
lineHeight = 24.sp,
fontFamily = naviFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
color = NaviPayColor.inputFieldSuccess,
)
}
}
@Composable
fun BankServerDownText() {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
NaviText(
text = stringResource(id = R.string.np_upi_lite_masthead_bank_server_down_title),
fontSize = 18.sp,
lineHeight = 24.sp,
fontFamily = naviFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
color = NaviPayColor.textPrimary,
)
NaviText(
text = stringResource(id = R.string.np_upi_lite_masthead_bank_server_down_sub_title),
fontSize = 18.sp,
lineHeight = 24.sp,
fontFamily = naviFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
color = NaviPayColor.inputFieldSuccess,
)
}
}
@Composable
fun BankStatementFullText() {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
NaviText(
text = stringResource(id = R.string.np_upi_lite_masthead_bank_statement_full_title),
fontSize = 18.sp,
lineHeight = 24.sp,
fontFamily = naviFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
color = NaviPayColor.textPrimary,
)
NaviText(
text = stringResource(id = R.string.np_upi_lite_masthead_bank_statement_full_sub_title),
fontSize = 18.sp,
lineHeight = 24.sp,
fontFamily = naviFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
color = NaviPayColor.inputFieldSuccess,
)
}
}
@Composable
fun MinimumAutoTopUpMessageSection(mandateThresholdAmount: Int) {
Row(

View File

@@ -23,6 +23,7 @@ import com.navi.common.R as CommonR
import com.navi.common.di.CoroutineDispatcherProvider
import com.navi.common.network.models.RepoResult
import com.navi.common.network.models.isSuccessWithData
import com.navi.common.usecase.LitmusExperimentsUseCase
import com.navi.common.utils.CommonUtils.getDisplayableAmount
import com.navi.common.utils.CommonUtils.isAmountValid
import com.navi.common.utils.Constants
@@ -95,6 +96,7 @@ import com.navi.pay.management.lite.models.view.UPILiteAccountStatus
import com.navi.pay.management.lite.models.view.UPILiteEntity
import com.navi.pay.management.lite.models.view.UpiLiteBottomSheetStateHolder
import com.navi.pay.management.lite.models.view.UpiLiteDefaultEnteredAmountExperimentData
import com.navi.pay.management.lite.models.view.UpiLiteUspExperimentVariant
import com.navi.pay.management.lite.repository.UPILiteRepository
import com.navi.pay.management.lite.util.AddBalanceButtonSource
import com.navi.pay.management.lite.util.DisableUpiLiteStatus
@@ -146,6 +148,7 @@ import com.navi.pay.utils.DEFAULT_UPI_MODE
import com.navi.pay.utils.KEY_UPI_LITE_ACTIVE_ACCOUNT_INFO
import com.navi.pay.utils.KEY_UPI_LITE_MANDATE_INFO
import com.navi.pay.utils.LITE_MANDATE
import com.navi.pay.utils.LITMUS_EXPERIMENT_NAVIPAY_UPI_LITE_USP
import com.navi.pay.utils.MONEY_ADDED_TO_UPI_LITE
import com.navi.pay.utils.NAVI_PAY_DEFAULT_MCC
import com.navi.pay.utils.NAVI_PAY_PURPLE_CTA_LOADER_LOTTIE
@@ -220,6 +223,7 @@ constructor(
private val paymentStatusRepository: PaymentStatusRepository,
private val upiLiteExperimentationUseCase: UpiLiteExperimentationUseCase,
val accountListCheckBalanceUseCase: AccountListCheckBalanceUseCase,
private val litmusExperimentsUseCase: LitmusExperimentsUseCase,
private val naviPayPspManager: NaviPayPspManager,
@NaviPayGsonBuilder private val gson: Gson,
private val arcNudgeUseCase: ArcNudgeUseCase,
@@ -395,6 +399,9 @@ constructor(
private val noOfAutoTopUpSupportedBanks = MutableStateFlow(0)
val isAutoTopUpSetUp = MutableStateFlow(false)
private val _upiLiteUspExperimentVariant = MutableStateFlow(UpiLiteUspExperimentVariant.DEFAULT)
val upiLiteUspExperimentVariant = _upiLiteUspExperimentVariant.asStateFlow()
private var previousLinkedAccountsSize = emptyList<String>()
private val _upiLiteMandateInfo = MutableStateFlow<MandateItem?>(null)
@@ -720,11 +727,19 @@ constructor(
)
val statusBarColor =
combine(uiState, isUpiLiteBalanceLow) { uiState, isUpiLiteBalanceLow ->
combine(uiState, isUpiLiteBalanceLow, upiLiteUspExperimentVariant) {
uiState,
isUpiLiteBalanceLow,
upiLiteExperimentVariant ->
if (uiState != UpiLiteUiState.MAIN_SCREEN) {
R.color.navi_pay_status_bar_default_color
} else if (isUpiLiteBalanceLow) {
R.color.navi_pay_bg_alt
} else if (
!isUserOnboarded.value &&
upiLiteExperimentVariant != UpiLiteUspExperimentVariant.DEFAULT
) {
R.color.navi_pay_status_bar_lottie_green
} else {
R.color.navi_pay_upi_lite_status_bar_color
}
@@ -913,6 +928,7 @@ constructor(
init {
fetchNaviPayUpiLiteConfig()
setLitmusExperimentValues()
setUpiLiteUspExperimentValues()
getLiteMandateInfo()
updateNaviPaySessionAttributes()
getLiteInfoIfActiveAccountIsPresent()
@@ -1117,6 +1133,29 @@ constructor(
}
}
private fun setUpiLiteUspExperimentValues() {
viewModelScope.launch(coroutineDispatcherProvider.io) {
val upiLiteUspExperimentVariantInfo =
litmusExperimentsUseCase
.execute(experimentName = LITMUS_EXPERIMENT_NAVIPAY_UPI_LITE_USP)
?.variant
val upiLiteUspExperimentVariantType =
if (upiLiteUspExperimentVariantInfo?.enabled == false) {
UpiLiteUspExperimentVariant.DEFAULT
} else {
when (upiLiteUspExperimentVariantInfo?.name) {
"v1_fast_txns" -> UpiLiteUspExperimentVariant.FAST_TRANSACTION
"v2_bank_downtime" -> UpiLiteUspExperimentVariant.BANK_SERVER_DOWN
"v3_clean_statement" -> UpiLiteUspExperimentVariant.SIMPLIFY_BANK_STATEMENTS
else -> UpiLiteUspExperimentVariant.DEFAULT
}
}
_upiLiteUspExperimentVariant.update { upiLiteUspExperimentVariantType }
}
}
fun updateSnackBarState(show: Boolean, messageId: Int = R.string.copied_to_clipboard) {
_snackBarState.update {
SnackBarState(isVisible = show, message = resourceProvider.getString(resId = messageId))

View File

@@ -167,6 +167,7 @@ const val LITMUS_EXPERIMENT_NAVIPAY_DISABLED_CHECK_BALANCE_DURING_TRANSACTION =
const val LITMUS_EXPERIMENT_NAVIPAY_SMV_BINDING = "NaviPay-exp-smv-binding"
const val LITMUS_EXPERIMENT_NAVI_FESTIVE_THEME = "festive-theme"
const val LITMUS_EXPERIMENT_NAVIPAY_OFFER_EXPERIENCE = "NaviPay-offer-experience"
const val LITMUS_EXPERIMENT_NAVIPAY_UPI_LITE_USP = "NaviPay-exp-lite-usp-exp"
val NAVI_PAY_LITMUS_EXPERIMENTS =
listOf(
LITMUS_EXPERIMENT_NAVIPAY_LITE_DEFAULT_ENTERED_AMOUNT,
@@ -175,6 +176,7 @@ val NAVI_PAY_LITMUS_EXPERIMENTS =
LITMUS_EXPERIMENT_NAVIPAY_SMV_BINDING,
LITMUS_EXPERIMENT_NAVI_FESTIVE_THEME,
LITMUS_EXPERIMENT_NAVIPAY_OFFER_EXPERIENCE,
LITMUS_EXPERIMENT_NAVIPAY_UPI_LITE_USP,
)
// Generic

View File

@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="21dp"
android:height="22dp"
android:viewportWidth="21"
android:viewportHeight="22">
<path
android:pathData="M19.244,9.665L18.929,11.571H4.803C4.895,11.02 5.167,10.543 5.55,10.205C5.931,9.866 6.423,9.665 6.953,9.665H19.244Z"
android:strokeWidth="0.0316992"
android:fillColor="#191919"
android:strokeColor="#000000"/>
<path
android:pathData="M18.488,14.253L18.173,16.16H1C1.093,15.609 1.364,15.132 1.747,14.793C2.128,14.454 2.62,14.253 3.15,14.253H18.488Z"
android:strokeWidth="0.0316992"
android:fillColor="#191919"
android:strokeColor="#000000"/>
<path
android:pathData="M20,5.076L19.816,6.194L19.686,6.982H8.607C8.7,6.431 8.971,5.954 9.354,5.615C9.736,5.276 10.228,5.076 10.758,5.076H20Z"
android:strokeWidth="0.0316992"
android:fillColor="#191919"
android:strokeColor="#000000"/>
</vector>

View File

@@ -14,4 +14,5 @@
<color name="navi_pay_dark_grey">#A8A8A8</color>
<color name="navi_pay_light_green">#E4FFED</color>
<color name="navi_pay_scratch_card_bg">#031106</color>
<color name="navi_pay_status_bar_lottie_green">#FAFFFC</color>
</resources>

View File

@@ -815,4 +815,10 @@
<string name="np_merchant_fee_info_desc">This fees is charged by merchants to process credit transactions.</string>
<string name="np_tip_removed">Tip removed</string>
<string name="np_tip_added">Tip added</string>
<string name="np_upi_lite_masthead_fast_payments_title">Super fast payments</string>
<string name="np_upi_lite_masthead_fast_payments_sub_title">No wait, no hassle!</string>
<string name="np_upi_lite_masthead_bank_server_down_title">Bank server down?</string>
<string name="np_upi_lite_masthead_bank_server_down_sub_title">Pay instantly with UPI Lite</string>
<string name="np_upi_lite_masthead_bank_statement_full_title">Bank statement full?</string>
<string name="np_upi_lite_masthead_bank_statement_full_sub_title">Just keep it Lite</string>
</resources>