TP-86113 | Narayan | Old Select bank screen code removal (#12791)

This commit is contained in:
Aditya Narayan Malik
2024-09-28 13:10:56 +05:30
committed by GitHub
parent 41c131d5dc
commit 03ba2d0833
12 changed files with 114 additions and 1950 deletions

View File

@@ -15,8 +15,6 @@ import com.navi.base.deeplink.DeepLinkManager
import com.navi.base.model.CtaData
import com.navi.base.sharedpref.PreferenceManager
import com.navi.base.utils.EMPTY
import com.navi.common.upi.ACCOUNT_TYPE
import com.navi.common.upi.IS_FRESH_ON_BOARDING
import com.navi.common.upi.PIN_ACTION
import com.navi.common.upi.PMS
import com.navi.common.upi.SEND_MONEY_SCREEN_SOURCE
@@ -39,7 +37,6 @@ import com.navi.pay.destinations.OrderHistoryScreenDestination
import com.navi.pay.destinations.PayToContactsScreenDestination
import com.navi.pay.destinations.QrScannerScreenDestination
import com.navi.pay.destinations.SavedBeneficiaryScreenDestination
import com.navi.pay.destinations.SelectBankScreenDestination
import com.navi.pay.destinations.SendMoneyScreenDestination
import com.navi.pay.destinations.SendMoneyScreenV2Destination
import com.navi.pay.destinations.SetPinConfirmationScreenDestination
@@ -51,7 +48,6 @@ import com.navi.pay.entry.NaviPayActivity
import com.navi.pay.management.common.sendmoney.model.view.SendMoneyScreenSource
import com.navi.pay.management.common.sendmoney.model.view.UpiTransactionType
import com.navi.pay.management.moneytransfer.scanpay.util.getPayeeEntity
import com.navi.pay.onboarding.account.add.model.view.AccountType
import com.navi.pay.onboarding.account.linked.model.view.LinkedAccountsScreenSource
import com.navi.pay.utils.ACTION_PIN_SET
import com.navi.pay.utils.BANK_ACCOUNT_UNIQUE_ID
@@ -153,15 +149,6 @@ object NaviPayRouter {
SetPinConfirmationScreenDestination(source = sourceScreenName)
NaviPayScreenType.NAVI_PAY_TO_CONTACTS.name ->
PayToContactsScreenDestination(openSearchState = false)
NaviPayScreenType.SELECT_BANK_SCREEN_ONBOARDING.name -> {
val isFreshOnBoardingCase = bundle.getBoolean(IS_FRESH_ON_BOARDING, true)
val accountType =
AccountType.getAccountType(bundle.getString(ACCOUNT_TYPE).orEmpty())
SelectBankScreenDestination(
isFreshOnBoardingCase = isFreshOnBoardingCase,
accountType = accountType
)
}
NaviPayScreenType.NAVI_PAY_SEND_MONEY_SCREEN.name -> {
val payeeEntity =
Uri.parse(bundle.getString(NAVI_PAY_UPI_URI_KEY)).getPayeeEntity(true)

View File

@@ -65,6 +65,7 @@ import com.navi.pay.common.theme.color.NaviPayColor
import com.navi.pay.common.ui.NaviPayModalBottomSheetLayout
import com.navi.pay.common.ui.ThemeRoundedButton
import com.navi.pay.common.utils.NaviPayCommonUtils.launchPermissionSettingsScreen
import com.navi.pay.common.utils.launchOnboardingSDK
import com.navi.pay.entry.NaviPayActivity
import com.navi.pay.management.common.paymentsummary.model.view.BottomBarCtaStateForRewards
import com.navi.pay.management.common.paymentsummary.model.view.PaymentSummaryBottomSheetUIState
@@ -225,6 +226,18 @@ fun PaymentSummaryScreenV2(
}
}
LaunchedEffect(Unit) {
paymentSummaryViewModel.onboardingSdkAction.collect {
paymentSummaryViewModel.clearOnboardingSdkReplayCache()
if (it != null) {
naviPayActivity.launchOnboardingSDK(
action = it.action,
enabledAccountTypes = it.enabledAccountTypes
)
}
}
}
LaunchedEffect(Unit) {
paymentSummaryViewModel.updateAreNotificationsEnabled(
areNotificationsEnabled = areNotificationsEnabled(context = naviPayActivity)

View File

@@ -91,6 +91,7 @@ import com.navi.pay.common.ui.NaviPayModalBottomSheetLayout
import com.navi.pay.common.ui.ThemeRoundedButton
import com.navi.pay.common.ui.TitleDescriptionWithLinearProgressBar
import com.navi.pay.common.utils.NaviPayCommonUtils.launchPermissionSettingsScreen
import com.navi.pay.common.utils.launchOnboardingSDK
import com.navi.pay.entry.NaviPayActivity
import com.navi.pay.management.common.paymentsummary.model.view.PaymentSummaryBottomSheetUIStateV2
import com.navi.pay.management.common.paymentsummary.model.view.PaymentSummaryScreenState
@@ -303,6 +304,18 @@ fun PaymentSummaryScreenV3(
)
}
LaunchedEffect(Unit) {
paymentSummaryViewModel.onboardingSdkAction.collect {
paymentSummaryViewModel.clearOnboardingSdkReplayCache()
if (it != null) {
naviPayActivity.launchOnboardingSDK(
action = it.action,
enabledAccountTypes = it.enabledAccountTypes
)
}
}
}
LaunchedEffect(Unit) {
paymentSummaryViewModel.navigateToNextScreenFromHelpCta.collect {
it?.let { NaviPayRouter.onCtaClick(naviPayActivity = naviPayActivity, ctaData = it) }

View File

@@ -45,7 +45,6 @@ import com.navi.pay.common.utils.NaviPayCommonUtils
import com.navi.pay.common.utils.combine
import com.navi.pay.common.viewmodel.NaviPayBaseVM
import com.navi.pay.common.widget.NaviPayWidgetManager
import com.navi.pay.destinations.SelectBankScreenDestination
import com.navi.pay.management.common.paymentsummary.model.view.BottomBarCtaStateForRewards
import com.navi.pay.management.common.paymentsummary.model.view.PaymentSummaryBottomSheetStateHolder
import com.navi.pay.management.common.paymentsummary.model.view.PaymentSummaryBottomSheetUIState
@@ -65,7 +64,10 @@ import com.navi.pay.onboarding.account.add.model.view.AccountType
import com.navi.pay.onboarding.account.add.model.view.AccountType.Companion.isAccountOfTypeCreditCardOrCreditLine
import com.navi.pay.onboarding.account.add.repository.BankRepository
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.onboardingV2.model.view.NaviPayOnboardingActionsV2Type
import com.navi.pay.onboardingV2.model.view.OnboardingIntentData
import com.navi.pay.permission.utils.PermissionStateProvider
import com.navi.pay.utils.CREDIT_CARD_ONLY_ENABLED_ACCOUNTS
import com.navi.pay.utils.ConfigKey
import com.navi.pay.utils.DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR
import com.navi.pay.utils.KEY_IS_FIRST_TRANSACTION_SUCCESSFUL
@@ -90,6 +92,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -200,6 +203,9 @@ constructor(
FirebaseRemoteConfigHelper.NAVI_PAY_POST_PAYMENT_SCREEN_BANNER_VIEW_LIMIT,
)
private val _onboardingSdkAction = MutableSharedFlow<OnboardingIntentData?>(replay = 1)
val onboardingSdkAction = _onboardingSdkAction.asSharedFlow()
private val isUpiNumberLinked: MutableStateFlow<Boolean?> = MutableStateFlow(null)
private val _widgetsRenderingOrder = MutableStateFlow<Map<String, Int>>(emptyMap())
@@ -389,11 +395,16 @@ constructor(
isEnabled = !isRccAccountPresent(linkedAccounts = linkedAccounts),
onClickAction = {
viewModelScope.launch(Dispatchers.IO) {
updateNavigationToNextScreen(
direction =
SelectBankScreenDestination(
isFreshOnBoardingCase = false,
accountType = AccountType.CREDIT
_onboardingSdkAction.emit(
value =
OnboardingIntentData(
action =
NaviPayOnboardingActionsV2Type.ACCOUNT_ADDITION.name,
enabledAccountTypes = CREDIT_CARD_ONLY_ENABLED_ACCOUNTS,
addAccountSuccessDescription = "",
selectedAccountId = "",
preferredBankCode = "",
source = NaviPayAnalytics.NAVI_PAY_PAYMENT_STATUS
)
)
}
@@ -414,11 +425,17 @@ constructor(
isEnabled = false,
onClickAction = {
viewModelScope.launch(Dispatchers.IO) {
updateNavigationToNextScreen(
direction =
SelectBankScreenDestination(
isFreshOnBoardingCase = false,
accountType = AccountType.CREDIT
_onboardingSdkAction.emit(
value =
OnboardingIntentData(
action =
NaviPayOnboardingActionsV2Type.ACCOUNT_ADDITION
.name,
enabledAccountTypes = CREDIT_CARD_ONLY_ENABLED_ACCOUNTS,
addAccountSuccessDescription = "",
selectedAccountId = "",
preferredBankCode = "",
source = NaviPayAnalytics.NAVI_PAY_PAYMENT_STATUS
)
)
}
@@ -652,6 +669,11 @@ constructor(
}
}
@OptIn(ExperimentalCoroutinesApi::class)
fun clearOnboardingSdkReplayCache() {
_onboardingSdkAction.resetReplayCache()
}
private fun fetchLitmusExperimentData() {
viewModelScope.launch(Dispatchers.IO) {
val experimentData =

View File

@@ -44,7 +44,6 @@ import com.navi.pay.common.utils.NaviPayCommonUtils
import com.navi.pay.common.utils.combine
import com.navi.pay.common.viewmodel.NaviPayBaseVM
import com.navi.pay.common.widget.NaviPayWidgetManager
import com.navi.pay.destinations.SelectBankScreenDestination
import com.navi.pay.management.common.paymentsummary.model.view.PaymentSummaryBottomSheetStateHolderV2
import com.navi.pay.management.common.paymentsummary.model.view.PaymentSummaryBottomSheetUIStateV2
import com.navi.pay.management.common.paymentsummary.model.view.PaymentSummaryRewardsGratificationUIStateV2
@@ -64,7 +63,10 @@ import com.navi.pay.onboarding.account.add.model.view.AccountType
import com.navi.pay.onboarding.account.add.model.view.AccountType.Companion.isAccountOfTypeCreditCardOrCreditLine
import com.navi.pay.onboarding.account.add.repository.BankRepository
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.onboardingV2.model.view.NaviPayOnboardingActionsV2Type
import com.navi.pay.onboardingV2.model.view.OnboardingIntentData
import com.navi.pay.permission.utils.PermissionStateProvider
import com.navi.pay.utils.CREDIT_CARD_ONLY_ENABLED_ACCOUNTS
import com.navi.pay.utils.ConfigKey
import com.navi.pay.utils.KEY_IS_FIRST_TRANSACTION_SUCCESSFUL
import com.navi.pay.utils.NAVI_PAY_AUTO_POPUP_SCRATCH_CARD_COUNTER
@@ -87,6 +89,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -210,6 +213,9 @@ constructor(
FirebaseRemoteConfigHelper.NAVI_PAY_POST_PAYMENT_SCREEN_BANNER_VIEW_LIMIT,
)
private val _onboardingSdkAction = MutableSharedFlow<OnboardingIntentData?>(replay = 1)
val onboardingSdkAction = _onboardingSdkAction.asSharedFlow()
private val isUpiNumberLinked: MutableStateFlow<Boolean?> = MutableStateFlow(null)
private val _widgetsRenderingOrder = MutableStateFlow<Map<String, Int>>(emptyMap())
@@ -399,11 +405,16 @@ constructor(
isEnabled = !isRccAccountPresent(linkedAccounts = linkedAccounts),
onClickAction = {
viewModelScope.launch(Dispatchers.IO) {
updateNavigationToNextScreen(
direction =
SelectBankScreenDestination(
isFreshOnBoardingCase = false,
accountType = AccountType.CREDIT
_onboardingSdkAction.emit(
value =
OnboardingIntentData(
action =
NaviPayOnboardingActionsV2Type.ACCOUNT_ADDITION.name,
enabledAccountTypes = CREDIT_CARD_ONLY_ENABLED_ACCOUNTS,
addAccountSuccessDescription = "",
selectedAccountId = "",
preferredBankCode = "",
source = NaviPayAnalytics.NAVI_PAY_PAYMENT_STATUS
)
)
}
@@ -424,11 +435,17 @@ constructor(
isEnabled = false,
onClickAction = {
viewModelScope.launch(Dispatchers.IO) {
updateNavigationToNextScreen(
direction =
SelectBankScreenDestination(
isFreshOnBoardingCase = false,
accountType = AccountType.CREDIT
_onboardingSdkAction.emit(
value =
OnboardingIntentData(
action =
NaviPayOnboardingActionsV2Type.ACCOUNT_ADDITION
.name,
enabledAccountTypes = CREDIT_CARD_ONLY_ENABLED_ACCOUNTS,
addAccountSuccessDescription = "",
selectedAccountId = "",
preferredBankCode = "",
source = NaviPayAnalytics.NAVI_PAY_PAYMENT_STATUS
)
)
}
@@ -769,6 +786,11 @@ constructor(
}
}
@OptIn(ExperimentalCoroutinesApi::class)
fun clearOnboardingSdkReplayCache() {
_onboardingSdkAction.resetReplayCache()
}
private fun fetchLitmusExperimentData() {
viewModelScope.launch(Dispatchers.IO) {
val experimentData =

View File

@@ -1,16 +0,0 @@
/*
*
* * Copyright © 2023 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.onboarding.account.add.model.view
import com.navi.pay.onboarding.account.add.viewmodel.SelectBankScreenBottomSheetUIState
data class SelectBankScreenBottomSheetHandler(
val showBottomSheet: Boolean,
val bottomSheetStateChange: Boolean,
val bottomSheetUIState: SelectBankScreenBottomSheetUIState
)

View File

@@ -1,382 +0,0 @@
/*
*
* * Copyright © 2022-2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.onboarding.account.add.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Divider
import androidx.compose.material.ModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.navi.common.R as CommonR
import com.navi.design.font.FontWeightEnum
import com.navi.design.theme.getFontWeight
import com.navi.design.theme.ttComposeFontFamily
import com.navi.naviwidgets.extensions.NaviText
import com.navi.pay.NavGraphs
import com.navi.pay.R
import com.navi.pay.analytics.NaviPayAnalytics
import com.navi.pay.common.theme.color.NaviPayColor
import com.navi.pay.common.ui.BottomSheetContentWithIconHeaderDescButton
import com.navi.pay.common.ui.BottomSheetContentWithIconHeaderPrimarySecondaryButtonCtaLoader
import com.navi.pay.common.ui.BottomSheetLoadingScreen
import com.navi.pay.common.ui.ImageWithBackground
import com.navi.pay.common.ui.NaviPayRadioButton
import com.navi.pay.common.ui.ThemeRoundedButton
import com.navi.pay.destinations.NaviPayLauncherScreenDestination
import com.navi.pay.onboarding.account.add.model.view.AccountType
import com.navi.pay.onboarding.account.add.viewmodel.SelectBankScreenBottomSheetUIState
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.onboardingV2.utils.getDescriptionTextIdForNoLinkedAccountsAsPerAccountType
import com.navi.pay.onboardingV2.utils.getHeaderTextIdForNoLinkedAccountsAsPerAccountType
import com.navi.pay.utils.clearBackStackUpToAndNavigate
import com.navi.pay.utils.clickableDebounce
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@Composable
fun SelectBankAccountBottomSheet(
bottomSheetState: ModalBottomSheetState,
selectBankScreenBottomSheetUIState: SelectBankScreenBottomSheetUIState,
naviPayAnalytics: NaviPayAnalytics.NaviPaySelectBank,
onAccountSearchNoAccountFoundButtonClicked: () -> Unit,
onAccountSelected: (LinkedAccountEntity) -> Unit,
onLinkAccountClicked: () -> Unit,
onAddAccountRetry: () -> Unit,
navigator: DestinationsNavigator,
accountType: AccountType,
onAddAccountErrorDismiss: () -> Unit,
onNpciRecommendationCtaClicked: (LinkedAccountEntity?) -> Unit,
onDismissClicked: () -> Unit
) {
when (selectBankScreenBottomSheetUIState) {
is SelectBankScreenBottomSheetUIState.Searching -> {
if (bottomSheetState.isVisible) {
naviPayAnalytics.onBankAccountsSearchStarted()
}
BottomSheetLoadingScreen(
headerTextId = R.string.searching_accounts,
descriptionTextId = R.string.searching_accounts_desc
)
}
is SelectBankScreenBottomSheetUIState.NotFound -> {
if (bottomSheetState.isVisible) {
naviPayAnalytics.onBankAccountsNotFound()
}
BottomSheetContentWithIconHeaderDescButton(
iconId = CommonR.drawable.ic_exclamation_red_border,
headerTextId =
getHeaderTextIdForNoLinkedAccountsAsPerAccountType(accountType = accountType),
descriptionTextId =
getDescriptionTextIdForNoLinkedAccountsAsPerAccountType(
accountType = accountType
),
buttonTextId = R.string.np_okay_got_it,
isPrimaryTypeButton = true,
onButtonClicked = { onAccountSearchNoAccountFoundButtonClicked.invoke() }
)
}
is SelectBankScreenBottomSheetUIState.Found -> {
if (bottomSheetState.isVisible) {
naviPayAnalytics.onBankAccountsFound(
selectBankScreenBottomSheetUIState.accountEntities
)
}
BankAccountFoundView(
accountEntities = selectBankScreenBottomSheetUIState.accountEntities,
onAccountSelected = onAccountSelected,
onLinkAccountClick = onLinkAccountClicked,
accountType = accountType,
onDismissClicked = onDismissClicked
)
}
is SelectBankScreenBottomSheetUIState.ReBind -> {
naviPayAnalytics.onBankAccountsReBind()
BottomSheetContentWithIconHeaderDescButton(
iconId = CommonR.drawable.ic_exclamation_red_border,
headerTextId = R.string.no_banks_found,
descriptionTextId = R.string.no_banks_found_desc,
buttonTextId = R.string.try_again,
onButtonClicked = {
navigator.clearBackStackUpToAndNavigate(
destination = NaviPayLauncherScreenDestination,
popUpTo = NavGraphs.root,
inclusive = true
)
}
)
}
is SelectBankScreenBottomSheetUIState.AddAccountError -> {
if (bottomSheetState.isVisible) {
naviPayAnalytics.onBankLinkFailure(selectBankScreenBottomSheetUIState.title)
}
BottomSheetContentWithIconHeaderPrimarySecondaryButtonCtaLoader(
header = selectBankScreenBottomSheetUIState.title,
description = selectBankScreenBottomSheetUIState.description,
primaryButton = stringResource(id = R.string.retry),
secondaryButton = stringResource(id = R.string.cancel),
onPrimaryButtonClicked = { onAddAccountRetry.invoke() },
onSecondaryButtonClicked = onAddAccountErrorDismiss
)
}
is SelectBankScreenBottomSheetUIState.NpciRecommendation -> {
val connectedAccount = selectBankScreenBottomSheetUIState.connectedAccount
NpciRecommendationBottomSheet(
onNpciRecommendationCtaClicked = {
onNpciRecommendationCtaClicked(connectedAccount)
}
)
}
else -> {
Box(modifier = Modifier.fillMaxWidth())
}
}
}
@Composable
private fun BankAccountFoundView(
accountEntities: List<LinkedAccountEntity>,
onAccountSelected: (LinkedAccountEntity) -> Unit,
onLinkAccountClick: () -> Unit,
accountType: AccountType,
onDismissClicked: () -> Unit
) {
Column(modifier = Modifier.fillMaxWidth().verticalScroll(state = rememberScrollState())) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
NaviText(
text =
stringResource(
id =
if (accountType != AccountType.CREDIT) R.string.select_bank_acc
else R.string.select_your_credit_card
),
fontSize = 16.sp,
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
color = NaviPayColor.textPrimary
)
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(id = CommonR.drawable.ic_close_black),
contentDescription = null,
modifier = Modifier.clickableDebounce { onDismissClicked.invoke() },
)
}
Divider(
modifier = Modifier.fillMaxWidth(),
color = NaviPayColor.borderAlt,
thickness = 1.dp
)
var selectedItemPosition by remember { mutableIntStateOf(0) }
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
repeat(accountEntities.size) { position ->
val accountEntity = accountEntities[position]
BankAccountEntityItem(
bankAccountEntity = accountEntity,
onSelected = {
selectedItemPosition = position
onAccountSelected.invoke(accountEntity)
},
isSelected = selectedItemPosition == position
)
}
}
Divider(
modifier = Modifier.fillMaxWidth(),
color = NaviPayColor.borderAlt,
thickness = 1.dp
)
Spacer(modifier = Modifier.height(16.dp))
ThemeRoundedButton(
modifier = Modifier.fillMaxWidth().padding(16.dp),
text = stringResource(id = R.string.continue_text),
cornerRadius = 4.dp
) {
onLinkAccountClick()
}
Spacer(modifier = Modifier.height(32.dp))
}
}
@Composable
private fun BankAccountEntityItem(
bankAccountEntity: LinkedAccountEntity,
onSelected: (LinkedAccountEntity) -> Unit,
isSelected: Boolean
) {
Column(
modifier =
Modifier.fillMaxWidth()
.background(
shape = RoundedCornerShape(1.dp),
color = if (isSelected) NaviPayColor.bgLightGreen else NaviPayColor.ctaWhite
)
.clickableDebounce { onSelected(bankAccountEntity) }
) {
Row(
modifier = Modifier.fillMaxWidth().padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
ImageWithBackground(
boxSize = 40.dp,
backgroundColor = NaviPayColor.ctaWhite,
backgroundCornerRadius = 4.dp,
imageUrl = bankAccountEntity.bankIconImageUrl,
imageSize = 36.dp
)
Spacer(modifier = Modifier.width(14.dp))
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
NaviText(
text = bankAccountEntity.bankNameAccountNumber,
fontSize = 16.sp,
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
color = NaviPayColor.textPrimary
)
Spacer(modifier = Modifier.width(8.dp))
Image(
painter = painterResource(id = R.drawable.upi_logo_colored),
contentDescription = null
)
}
Spacer(modifier = Modifier.height(2.dp))
NaviText(
text = bankAccountEntity.accountTypeFormatted,
fontSize = 12.sp,
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR),
color = NaviPayColor.textTertiary
)
}
Spacer(modifier = Modifier.weight(1f))
NaviPayRadioButton(
isSelected = isSelected,
onCheckedChange = { onSelected(bankAccountEntity) }
)
}
}
}
@Composable
private fun NpciRecommendationBottomSheet(onNpciRecommendationCtaClicked: () -> Unit) {
Column(
modifier =
Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 32.dp)
) {
NaviText(
text = stringResource(id = R.string.navi_pay_npci_recommendation_header),
fontSize = 18.sp,
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
color = NaviPayColor.textPrimary
)
Spacer(modifier = Modifier.height(16.dp))
NpciRecommendationBottomSheetDescriptionItem(
imageId = R.drawable.ic_np_npci_recommendation_verify_sender_id,
descriptionId = R.string.navi_pay_npci_recommendation_verify_sender_id
)
Spacer(modifier = Modifier.height(12.dp))
NpciRecommendationBottomSheetDescriptionItem(
imageId = R.drawable.ic_np_npci_recommendation_change_pin,
descriptionId = R.string.navi_pay_npci_recommendation_change_pin
)
Spacer(modifier = Modifier.height(12.dp))
NpciRecommendationBottomSheetDescriptionItem(
imageId = R.drawable.ic_np_npci_recommendation_donot_share_pin,
descriptionId = R.string.navi_pay_npci_recommendation_donot_share_pin
)
Spacer(modifier = Modifier.height(12.dp))
NpciRecommendationBottomSheetDescriptionItem(
imageId = R.drawable.ic_np_npci_recommendation_collect_request_mandate,
descriptionId = R.string.navi_pay_npci_recommendation_collect_request_mandate
)
Spacer(modifier = Modifier.height(24.dp))
ThemeRoundedButton(
text = stringResource(id = R.string.np_okay_got_it),
modifier = Modifier.fillMaxWidth(),
onClick = onNpciRecommendationCtaClicked
)
}
}
@Composable
private fun NpciRecommendationBottomSheetDescriptionItem(imageId: Int, descriptionId: Int) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Image(
painter = painterResource(id = imageId),
contentDescription = null,
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(14.dp))
NaviText(
text = stringResource(id = descriptionId),
fontSize = 14.sp,
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR),
color = NaviPayColor.textSecondary
)
}
}

View File

@@ -1,918 +0,0 @@
/*
*
* * Copyright © 2022-2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.onboarding.account.add.ui
import android.view.WindowManager
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.IconButton
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import coil.compose.AsyncImage
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
import com.navi.common.R as CommonR
import com.navi.common.upi.NaviPayAction
import com.navi.common.upi.UPI_RESULT_CODE
import com.navi.design.font.FontWeightEnum
import com.navi.design.theme.getFontWeight
import com.navi.design.theme.ttComposeFontFamily
import com.navi.design.utils.clickableWithNoGesture
import com.navi.design.utils.maxScrollFlingBehavior
import com.navi.naviwidgets.extensions.NaviText
import com.navi.pay.NavGraphs
import com.navi.pay.R
import com.navi.pay.analytics.NaviPayAnalytics
import com.navi.pay.common.model.view.SetPinResult
import com.navi.pay.common.setup.NaviPayRouter
import com.navi.pay.common.setup.model.NaviPayCustomerStatus
import com.navi.pay.common.theme.color.NaviPayColor
import com.navi.pay.common.ui.ImageWithBackground
import com.navi.pay.common.ui.LoadingScreen
import com.navi.pay.common.ui.NaviPayCreditCardSponsorView
import com.navi.pay.common.ui.NaviPayCreditLineSponsorView
import com.navi.pay.common.ui.NaviPayHeader
import com.navi.pay.common.ui.NaviPayModalBottomSheetLayout
import com.navi.pay.common.ui.NaviPaySponsorView
import com.navi.pay.common.ui.RenderAPIResultScreen
import com.navi.pay.common.utils.ErrorEventHandler
import com.navi.pay.common.utils.NaviPayCommonUtils.closeKeyboardOnScroll
import com.navi.pay.common.utils.NaviPayEventBus
import com.navi.pay.common.utils.NaviPayMediaPlayer
import com.navi.pay.common.utils.NaviPaySdkUtils
import com.navi.pay.destinations.LinkedAccountVerifyScreenDestination
import com.navi.pay.destinations.LinkedAccountsScreenV2Destination
import com.navi.pay.entry.NaviPayActivity
import com.navi.pay.onboarding.account.add.model.view.AccountType
import com.navi.pay.onboarding.account.add.model.view.BankEntity
import com.navi.pay.onboarding.account.add.viewmodel.SelectBankScreenBottomSheetUIState
import com.navi.pay.onboarding.account.add.viewmodel.SelectBankScreenStates
import com.navi.pay.onboarding.account.add.viewmodel.SelectBankViewModel
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.onboarding.account.linked.model.view.LinkedAccountsScreenSource
import com.navi.pay.utils.ACTION_PIN_SET
import com.navi.pay.utils.BANK_NAME
import com.navi.pay.utils.DEEPLINK_URL
import com.navi.pay.utils.NAVI_PAY_ADD_ACCOUNT_REWARDS_LOTTIE
import com.navi.pay.utils.NAVI_PAY_LOADER
import com.navi.pay.utils.NAVI_PAY_SUCCESS_LOTTIE
import com.navi.pay.utils.PHONE_NUMBER_LENGTH
import com.navi.pay.utils.UPI_NUMBER_LINK
import com.navi.pay.utils.VPA
import com.navi.pay.utils.clearBackStackUpToAndNavigate
import com.navi.pay.utils.clickableDebounce
import com.navi.pay.utils.customHide
import com.navi.pay.utils.getImageRequestBuilder
import com.navi.pay.utils.hideSheet
import com.navi.pay.utils.isEmpty
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.result.ResultRecipient
import java.lang.ref.WeakReference
import kotlinx.coroutines.launch
@Destination
@Composable
fun SelectBankScreen(
naviPayActivity: NaviPayActivity,
selectBankViewModel: SelectBankViewModel = hiltViewModel(),
isFreshOnBoardingCase: Boolean = true,
accountType: AccountType = AccountType.SAVINGS,
resultRecipient: ResultRecipient<LinkedAccountVerifyScreenDestination, SetPinResult>,
navigator: DestinationsNavigator,
naviPayAnalytics: NaviPayAnalytics.NaviPaySelectBank =
NaviPayAnalytics.INSTANCE.NaviPaySelectBank(),
isIntentOfSelfTransfer: Boolean = false,
) {
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(key1 = lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
naviPayActivity.window.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
)
} else if (event == Lifecycle.Event.ON_STOP) {
naviPayActivity.window.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
)
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
naviPayActivity.window.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
)
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
LaunchedEffect(Unit) { naviPayAnalytics.onNaviPaySelectBankLanded(accountType.name) }
val uiState by selectBankViewModel.uiState.collectAsStateWithLifecycle()
val bottomSheetStateHolder by
selectBankViewModel.bottomSheetStateHolder.collectAsStateWithLifecycle()
val popularBanks by selectBankViewModel.popularBanks.collectAsStateWithLifecycle()
val allBanks = selectBankViewModel.allBankListPager.collectAsLazyPagingItems()
val searchQuery by selectBankViewModel.searchQuery.collectAsStateWithLifecycle()
val rewardsNudgeDetailEntity by
selectBankViewModel.rewardsNudgeDetailEntity.collectAsStateWithLifecycle()
val shouldShowSponsor by selectBankViewModel.shouldShowSponsor.collectAsStateWithLifecycle()
val customerMobileNumber = remember { selectBankViewModel.customerPhoneNumber }
val isTrailingIconEnabled by
selectBankViewModel.isTrailingIconEnabled.collectAsStateWithLifecycle()
val bottomSheetState =
rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
confirmValueChange = { bottomSheetStateHolder.bottomSheetStateChange }
)
val scope = rememberCoroutineScope()
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
val context = LocalContext.current
val view = LocalView.current
BackHandler(
(bottomSheetState.isVisible && bottomSheetStateHolder.bottomSheetStateChange) ||
naviPayActivity.viewModel.isWithoutOnBoardingFlow(naviPayActivity.intent)
) {
if (bottomSheetState.isVisible && bottomSheetStateHolder.bottomSheetStateChange) {
scope.launch { bottomSheetState.hideSheet() }
} else {
naviPayActivity.setResult(
UPI_RESULT_CODE,
NaviPaySdkUtils.getErrorResponseIntent(
if (isFreshOnBoardingCase) NaviPayAction.ONBOARDING.name
else NaviPayAction.ADD_BANK_ACCOUNT.name
)
)
naviPayActivity.finish()
}
}
val onBackClick: () -> Unit = {
if (naviPayActivity.viewModel.isWithoutOnBoardingFlow(naviPayActivity.intent)) {
naviPayActivity.setResult(
UPI_RESULT_CODE,
NaviPaySdkUtils.getErrorResponseIntent(
if (isFreshOnBoardingCase) NaviPayAction.ONBOARDING.name
else NaviPayAction.ADD_BANK_ACCOUNT.name
)
)
naviPayActivity.finish()
} else if (isFreshOnBoardingCase) {
naviPayActivity.onBackPressed()
} else {
navigator.navigateUp()
}
}
val finishActivityWithResult: () -> Unit = {
naviPayActivity.setResult(
UPI_RESULT_CODE,
NaviPaySdkUtils.getOnboardingSuccessIntent(
linkedAccountsEntity = selectBankViewModel.linkedAccounts,
customerStatusAfterOnboarding = NaviPayCustomerStatus.LINKED_VPA.name,
type =
if (isFreshOnBoardingCase) NaviPayAction.ONBOARDING.name
else NaviPayAction.ADD_BANK_ACCOUNT.name
)
)
naviPayActivity.finish()
}
LaunchedEffect(key1 = bottomSheetStateHolder.showBottomSheet) {
when (bottomSheetStateHolder.showBottomSheet) {
true -> scope.launch { bottomSheetState.show() }
false -> scope.launch { bottomSheetState.hide() }
}
}
LaunchedEffect(Unit) {
snapshotFlow { bottomSheetState.currentValue }
.collect {
if (it == ModalBottomSheetValue.Hidden) {
selectBankViewModel.updateBottomSheetUIState(showBottomSheet = false)
}
}
}
LaunchedEffect(Unit) {
selectBankViewModel.navigateToNextScreenFromHelpCta.collect {
it?.let { NaviPayRouter.onCtaClick(naviPayActivity = naviPayActivity, ctaData = it) }
}
}
val navigateToNextScreen = {
naviPayAnalytics.onNavigationToNextScreen(isFreshOnBoardingCase)
if (isFreshOnBoardingCase) {
// After account addition, redirect user to screen which he clicked on home screen
val nextScreenUrl =
naviPayActivity.viewModel.getSourceScreenUrl(naviPayActivity.intent?.data)
if (naviPayActivity.viewModel.isHomeScreenDestination(nextScreenUrl)) {
naviPayActivity.finish()
} else if (naviPayActivity.viewModel.isWithoutOnBoardingFlow(naviPayActivity.intent)) {
finishActivityWithResult.invoke()
} else {
val nextDestination =
NaviPayRouter.getDirectionFromCtaUrl(
url = nextScreenUrl,
sourceScreenName = NaviPayAnalytics.NAVI_PAY_ADD_ACCOUNT
) ?: NavGraphs.root
navigator.clearBackStackUpToAndNavigate(
destination = nextDestination,
popUpTo = NavGraphs.root,
inclusive = true
)
}
} else if (naviPayActivity.viewModel.isWithoutOnBoardingFlow(naviPayActivity.intent)) {
finishActivityWithResult.invoke()
} else {
if (selectBankViewModel.upiNumberBottomSheetNeedToShow.value) {
naviPayActivity.intent.putExtra(VPA, selectBankViewModel.vpa)
naviPayActivity.intent.putExtra(BANK_NAME, selectBankViewModel.bankName)
naviPayActivity.intent.putExtra(DEEPLINK_URL, UPI_NUMBER_LINK)
}
navigator.navigateUp()
}
Unit
}
val onAddAccountSuccess = { connectedAccount: LinkedAccountEntity? ->
connectedAccount?.let {
naviPayAnalytics.onAddAccountSuccess(
account = connectedAccount,
rewardNudgeShown =
if (!isFreshOnBoardingCase && rewardsNudgeDetailEntity != null) "Y" else "N"
)
if (isIntentOfSelfTransfer && connectedAccount.isMPinSet) {
navigator.navigate(
LinkedAccountsScreenV2Destination(
isFromAddBankAccount = true,
linkedAccountsScreenSourceFromNavigation =
LinkedAccountsScreenSource.SelfTransfer
)
)
} else if (connectedAccount.isMPinSet) {
navigateToNextScreen()
} else {
naviPayActivity.shouldFinishAfterResult = false
navigator.navigate(
LinkedAccountVerifyScreenDestination(
bankAccountUniqueId = connectedAccount.accountId,
actionName = ACTION_PIN_SET
)
)
}
}
}
resultRecipient.onNavResult { result ->
naviPayAnalytics.onPinSetCallback(result)
// if intent of self pay and user doesn't set pin then navigate to self pay screen
if (isIntentOfSelfTransfer) {
navigator.navigate(
LinkedAccountsScreenV2Destination(
linkedAccountsScreenSourceFromNavigation =
LinkedAccountsScreenSource.SelfTransfer
)
)
}
// Navigation to next screen no matter pin set was successful or not as user can set pin
// later in send money
else {
navigateToNextScreen()
}
}
val onTrailingIconClicked = {
selectBankViewModel.updateSearchQueryStringState(searchQuery = "")
}
LaunchedEffect(Unit) {
selectBankViewModel.navigateToNextScreen.collect {
if (!bottomSheetState.isVisible) {
onAddAccountSuccess(it)
}
}
}
LaunchedEffect(Unit) {
ErrorEventHandler.errorCtaClickEvent.collect {
NaviPayEventBus.resetEventBus()
if (it.errorConfig.tag == "onBankAccountsFailure") {
naviPayAnalytics.onBankAccountsFailure()
}
}
}
NaviPayModalBottomSheetLayout(
sheetState = bottomSheetState,
sheetContent = {
SelectBankAccountBottomSheet(
bottomSheetState = bottomSheetState,
selectBankScreenBottomSheetUIState = bottomSheetStateHolder.bottomSheetUIState,
naviPayAnalytics = naviPayAnalytics,
onAccountSearchNoAccountFoundButtonClicked = {
scope.launch {
selectBankViewModel.updateBottomSheetUIState(showBottomSheet = false)
}
},
onAccountSelected = selectBankViewModel::onAccountSelected,
onLinkAccountClicked = selectBankViewModel::linkAccount,
onAddAccountRetry = {
selectBankViewModel.selectedBank?.let {
keyboardController?.customHide(context = context, view = view)
selectBankViewModel.onBankSelected(bankEntity = it)
}
},
navigator = navigator,
accountType = accountType,
onAddAccountErrorDismiss = {
scope.launch {
selectBankViewModel.updateBottomSheetUIState(showBottomSheet = false)
}
},
onNpciRecommendationCtaClicked = { onAddAccountSuccess(it) },
onDismissClicked = {
scope.launch {
selectBankViewModel.updateBottomSheetUIState(showBottomSheet = false)
}
}
)
}
) {
when (uiState) {
SelectBankScreenStates.Loader -> LoadingScreen()
SelectBankScreenStates.SelectBank -> {
RenderSelectBankScreen(
popularBanks = popularBanks,
allBanks = allBanks,
keyboardController = keyboardController,
accountType = accountType,
onBankSelected = {
keyboardController?.customHide(context = context, view = view)
focusManager.clearFocus()
naviPayAnalytics.onBankClicked(it)
selectBankViewModel.onBankSelected(bankEntity = it)
},
searchQuery = searchQuery,
onSearchInputValueChange = {
selectBankViewModel.updateSearchQueryStringState(searchQuery = it)
naviPayAnalytics.onSearchBank(it)
},
customerMobileNumber = customerMobileNumber,
isTrailingIconEnabled = isTrailingIconEnabled,
onTrailingIconClicked = onTrailingIconClicked,
helpCtaText = selectBankViewModel.helpCtaData?.title,
onHelpCtaClicked = selectBankViewModel::onHelpClicked,
onBackClick = onBackClick,
shouldShowSponsor = shouldShowSponsor
)
}
is SelectBankScreenStates.AddBankSuccess -> {
// This check is here to avoid showing success animation again after coming back
// from another screen i.e. set pin
if (selectBankViewModel.isAddBankSuccessShown) {
return@NaviPayModalBottomSheetLayout
}
val connectedAccount =
(uiState as SelectBankScreenStates.AddBankSuccess).connectedAccount
val composition by
rememberLottieComposition(
LottieCompositionSpec.Asset(
if (!isFreshOnBoardingCase && rewardsNudgeDetailEntity != null)
NAVI_PAY_ADD_ACCOUNT_REWARDS_LOTTIE
else NAVI_PAY_SUCCESS_LOTTIE
)
)
val mediaPlayer = remember {
NaviPayMediaPlayer(activityRef = WeakReference(naviPayActivity))
}
val progress by animateLottieCompositionAsState(composition)
var isAnimationAudioCompleted by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
mediaPlayer.start(
fileNameResId = R.raw.navi_pay_bank_account_success,
onFinish = { isAnimationAudioCompleted = true }
)
}
if (progress == 1.0f && isAnimationAudioCompleted) {
LaunchedEffect(Unit) {
selectBankViewModel.onAddAccountSuccess(connectedAccount)
}
}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.weight(1f))
LottieAnimation(
composition = composition,
modifier = Modifier.size(140.dp),
iterations = 1,
)
Spacer(modifier = Modifier.height(24.dp))
NaviText(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringResource(id = R.string.account_added_successfully),
fontSize = 16.sp,
fontFamily = ttComposeFontFamily,
color = NaviPayColor.textPrimary,
fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
textAlign = TextAlign.Center
)
if (isFreshOnBoardingCase) {
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier =
Modifier.clickableDebounce {
scope.launch {
selectBankViewModel.updateBottomSheetUIState(
selectBankScreenBottomSheetUIState =
SelectBankScreenBottomSheetUIState
.NpciRecommendation(connectedAccount),
allowStateChange = false,
showBottomSheet = true
)
}
}
) {
Image(
painter = painterResource(id = R.drawable.ic_np_white_info_grey_bg),
contentDescription = "",
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(4.dp))
NaviText(
text =
stringResource(id = R.string.navi_pay_npci_how_to_use_header),
fontSize = 12.sp,
fontFamily = ttComposeFontFamily,
color = NaviPayColor.textTertiary,
fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
textDecoration = TextDecoration.Underline
)
}
} else if (rewardsNudgeDetailEntity != null) {
val addAccountRewardsMessage = buildAnnotatedString {
val text =
stringResource(id = R.string.np_add_account_rewards_message) +
stringResource(
id = R.string.np_rewards_won_suffix,
"${rewardsNudgeDetailEntity?.formattedAmount}"
)
val highlightedText =
stringResource(
id = R.string.np_rewards_won_suffix,
"${rewardsNudgeDetailEntity?.formattedAmount}"
)
append(text = text)
addStyle(
style =
SpanStyle(
fontFamily = ttComposeFontFamily,
fontWeight =
getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR),
fontSize = 14.sp,
color = NaviPayColor.textTertiary
),
start = 0,
end = text.length
)
addStyle(
style =
SpanStyle(
fontFamily = ttComposeFontFamily,
fontWeight =
getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
fontSize = 14.sp,
color = NaviPayColor.textTertiary
),
start = text.indexOf(highlightedText),
end = text.length
)
}
NaviText(text = addAccountRewardsMessage, textAlign = TextAlign.Center)
}
Spacer(modifier = Modifier.weight(1f))
}
}
SelectBankScreenStates.AddBankInProgress -> {
RenderAPIResultScreen(
lottieFileName = NAVI_PAY_LOADER,
titleText = stringResource(id = R.string.account_adding_in_progress_header),
descriptionText =
stringResource(id = R.string.account_adding_in_progress_description),
showLottieInfiniteTimes = true
)
}
}
}
}
@Composable
fun RenderSelectBankScreen(
popularBanks: List<BankEntity>,
allBanks: LazyPagingItems<BankEntity>,
keyboardController: SoftwareKeyboardController?,
accountType: AccountType,
onBankSelected: (BankEntity) -> Unit,
searchQuery: String,
onSearchInputValueChange: (String) -> Unit,
customerMobileNumber: String,
isTrailingIconEnabled: Boolean,
onTrailingIconClicked: () -> Unit,
helpCtaText: String?,
onHelpCtaClicked: () -> Unit,
onBackClick: () -> Unit,
shouldShowSponsor: Boolean
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val view = LocalView.current
Column(
modifier =
Modifier.fillMaxSize()
.nestedScroll(
closeKeyboardOnScroll(
scope = scope,
context = context,
view = view,
keyboardController = keyboardController
)
),
) {
NaviPayHeader(
title = stringResource(R.string.select_bank),
onNavigationIconClick = onBackClick,
actionIconText = helpCtaText,
onActionClick = onHelpCtaClicked,
modifier = Modifier.fillMaxWidth()
)
LazyColumn(modifier = Modifier.fillMaxSize(), flingBehavior = maxScrollFlingBehavior()) {
item {
Spacer(modifier = Modifier.height(16.dp))
NaviText(
text =
getAddAccountText(
accountType = accountType,
customerMobileNumber = customerMobileNumber
),
fontFamily = ttComposeFontFamily,
fontSize = 18.sp,
fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
color = NaviPayColor.textPrimary,
modifier = Modifier.padding(horizontal = 16.dp)
)
Spacer(modifier = Modifier.height(16.dp))
BankSearchField(
modifier = Modifier.padding(horizontal = 16.dp),
keyboardController = keyboardController,
searchQuery = searchQuery,
onSearchInputValueChange = onSearchInputValueChange,
isTrailingIconEnabled = isTrailingIconEnabled,
onTrailingIconClicked = onTrailingIconClicked
)
Spacer(modifier = Modifier.height(32.dp))
}
if (
accountType == AccountType.SAVINGS &&
popularBanks.isNotEmpty() &&
searchQuery.isEmpty()
) {
item {
PopularBankSection(popularBanks = popularBanks, onBankSelected = onBankSelected)
}
}
if (allBanks.itemCount > 0) {
item {
NaviText(
text = stringResource(id = R.string.all_banks_in_the_town),
fontFamily = ttComposeFontFamily,
fontSize = 14.sp,
fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
color = NaviPayColor.textTertiary,
modifier = Modifier.padding(horizontal = 16.dp)
)
}
}
items(count = allBanks.itemCount, key = allBanks.itemKey { it.code }) { index ->
val bankEntityItem = allBanks[index]
bankEntityItem?.let {
RegularBankView(bankEntity = it, onBankSelected = onBankSelected)
}
}
if (allBanks.isEmpty()) {
item {
NaviText(
text = stringResource(id = R.string.couldnt_find_this_bank),
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR),
fontSize = 12.sp,
color = NaviPayColor.textSecondary,
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)
)
}
}
if (!allBanks.isEmpty() && searchQuery.isEmpty() && shouldShowSponsor) {
item {
Spacer(modifier = Modifier.height(16.dp))
when (accountType.name) {
AccountType.CREDIT.name -> {
NaviPayCreditCardSponsorView(modifier = Modifier.fillMaxWidth())
}
AccountType.UPICREDIT.name -> {
NaviPayCreditLineSponsorView(modifier = Modifier.fillMaxWidth())
}
else -> {
NaviPaySponsorView(modifier = Modifier.fillMaxWidth())
}
}
Spacer(modifier = Modifier.height(32.dp))
}
}
}
}
}
@Composable
fun BankSearchField(
modifier: Modifier,
keyboardController: SoftwareKeyboardController?,
searchQuery: String,
onSearchInputValueChange: (String) -> Unit,
isTrailingIconEnabled: Boolean,
onTrailingIconClicked: () -> Unit
) {
val context = LocalContext.current
val view = LocalView.current
OutlinedTextField(
value = searchQuery,
onValueChange = { onSearchInputValueChange.invoke(it) },
singleLine = true,
maxLines = 1,
colors =
TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = NaviPayColor.ctaPrimary,
unfocusedBorderColor = NaviPayColor.borderAlt,
backgroundColor = NaviPayColor.transparent
),
textStyle =
TextStyle(
fontSize = 14.sp,
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
color = NaviPayColor.inputFieldFilled
),
keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Search),
keyboardActions =
KeyboardActions(
onSearch = { keyboardController?.customHide(context = context, view = view) }
),
modifier = modifier.fillMaxWidth().background(color = NaviPayColor.bgDefault),
placeholder = {
NaviText(
text = stringResource(id = R.string.search_bank_here),
fontSize = 14.sp,
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR),
color = NaviPayColor.textTertiary
)
},
trailingIcon = {
if (isTrailingIconEnabled) {
IconButton(onClick = { onTrailingIconClicked.invoke() }) {
Image(
painter = painterResource(id = CommonR.drawable.ic_close_black),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
}
}
}
)
}
@Composable
fun PopularBankSection(popularBanks: List<BankEntity>, onBankSelected: (BankEntity) -> Unit) {
NaviText(
text = stringResource(id = R.string.np_popular_banks),
fontFamily = ttComposeFontFamily,
fontSize = 14.sp,
fontWeight = getFontWeight(FontWeightEnum.NAVI_HEADLINE_REGULAR),
color = NaviPayColor.textTertiary,
modifier = Modifier.padding(horizontal = 16.dp)
)
// 1 row can contain max 3 banks
// height of 1 row is 110.dp including spacer = 56 + 8 + 22 + 24
val gridTotalHeight =
remember(popularBanks) {
val numberOfRows = (popularBanks.size + 2) / 3
(numberOfRows * 110).dp + 16.dp + 8.dp
}
LazyVerticalGrid(
modifier = Modifier.fillMaxWidth().height(gridTotalHeight),
contentPadding = PaddingValues(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 32.dp),
horizontalArrangement = Arrangement.SpaceBetween,
columns = GridCells.Fixed(3),
verticalArrangement = Arrangement.spacedBy(24.dp),
userScrollEnabled = false
) {
items(items = popularBanks, key = { it.code }) {
PopularBankView(bankEntity = it, onBankSelected = onBankSelected)
}
}
}
@Composable
fun PopularBankView(
bankEntity: BankEntity,
onBankSelected: (BankEntity) -> Unit,
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier =
Modifier.clickableWithNoGesture { onBankSelected(bankEntity) }
.padding(horizontal = 4.dp)
) {
ImageWithBackground(
boxSize = 56.dp,
backgroundCornerRadius = 2.dp,
imageUrl = bankEntity.iconUrl,
imageSize = 40.dp,
backgroundColor = NaviPayColor.bgAlt
)
Spacer(modifier = Modifier.height(8.dp))
NaviText(
text = bankEntity.name,
fontSize = 12.sp,
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR),
color = NaviPayColor.textPrimary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@Composable
fun RegularBankView(
bankEntity: BankEntity,
onBankSelected: (BankEntity) -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier.fillMaxWidth().clickableDebounce { onBankSelected(bankEntity) }.padding(16.dp)
) {
Box(
contentAlignment = Alignment.Center,
modifier =
Modifier.size(32.dp)
.border(
border = BorderStroke(width = 1.dp, color = NaviPayColor.borderAlt),
shape = RoundedCornerShape(2.dp)
)
.background(color = NaviPayColor.bgDefault)
) {
AsyncImage(
model = LocalContext.current.getImageRequestBuilder(data = bankEntity.iconUrl),
contentDescription = "",
modifier = Modifier.size(24.dp).align(Alignment.Center),
fallback = painterResource(id = CommonR.drawable.ic_upi_bbps_default_bank_logo),
placeholder = painterResource(id = CommonR.drawable.ic_upi_bbps_default_bank_logo),
error = painterResource(id = CommonR.drawable.ic_upi_bbps_default_bank_logo),
)
}
Spacer(modifier = Modifier.width(12.dp))
NaviText(
text = bankEntity.name,
fontSize = 14.sp,
fontFamily = ttComposeFontFamily,
fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR),
color = NaviPayColor.textPrimary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
@Composable
fun getAddAccountText(accountType: AccountType, customerMobileNumber: String): String {
val resId =
when (accountType) {
AccountType.SAVINGS -> R.string.add_a_bank_account
AccountType.CREDIT -> R.string.select_credit_card_bank_header
AccountType.UPICREDIT -> R.string.select_credit_line_header
}
return stringResource(id = resId, customerMobileNumber.takeLast(PHONE_NUMBER_LENGTH))
}

View File

@@ -1,551 +0,0 @@
/*
*
* * Copyright © 2022-2024 by Navi Technologies Limited
* * All rights reserved. Strictly confidential
*
*/
package com.navi.pay.onboarding.account.add.viewmodel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.navi.base.model.CtaData
import com.navi.base.utils.BaseUtils
import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper
import com.navi.common.model.common.NudgeDetailEntity
import com.navi.common.network.models.isSuccess
import com.navi.common.network.models.isSuccessWithData
import com.navi.common.usecase.RewardsNudgeEntityFetchUseCase
import com.navi.pay.analytics.NaviPayAnalytics
import com.navi.pay.common.connectivity.NaviPayNetworkConnectivity
import com.navi.pay.common.model.view.NaviPayScreenType
import com.navi.pay.common.model.view.NaviPayVmData
import com.navi.pay.common.repository.SharedPreferenceRepository
import com.navi.pay.common.setup.NaviPayCustomerStatusHandler
import com.navi.pay.common.setup.model.NaviPayCustomerStatus
import com.navi.pay.common.usecase.LinkedAccountsUseCase
import com.navi.pay.common.usecase.RefreshBankListUseCase
import com.navi.pay.common.usecase.RefreshLinkedAccountsUseCase
import com.navi.pay.common.utils.DeviceInfoProvider
import com.navi.pay.common.utils.NaviPayCommonUtils
import com.navi.pay.common.utils.NaviPayCommonUtils.getHelpCtaData
import com.navi.pay.common.viewmodel.NaviPayBaseVM
import com.navi.pay.management.upinumber.list.model.network.UpiNumbersListRequest
import com.navi.pay.onboarding.account.add.model.network.AccountItemResponse
import com.navi.pay.onboarding.account.add.model.network.AddAccountRequest
import com.navi.pay.onboarding.account.add.model.network.FetchAccountsRequest
import com.navi.pay.onboarding.account.add.model.network.toLinkedAccountEntity
import com.navi.pay.onboarding.account.add.model.view.AccountType
import com.navi.pay.onboarding.account.add.model.view.BankEntity
import com.navi.pay.onboarding.account.add.model.view.SelectBankScreenBottomSheetHandler
import com.navi.pay.onboarding.account.add.repository.BankRepository
import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity
import com.navi.pay.utils.NAVI_PAY_ENCRYPT_SHARED_PREF_DATA_KEYS
import com.navi.pay.utils.NAVI_PAY_NON_ENCRYPT_SHARED_PREF_DATA_KEYS
import com.navi.pay.utils.PHONE_NUMBER_LENGTH
import com.navi.pay.utils.UPI_NUMBER_STATUS_ACTIVE
import com.navi.pay.utils.getBankNameAccountNumberText
import com.navi.pay.utils.refresh
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@HiltViewModel
class SelectBankViewModel
@Inject
constructor(
private val bankRepository: BankRepository,
private val deviceInfoProvider: DeviceInfoProvider,
private val sharedPreferenceRepository: SharedPreferenceRepository,
private val refreshLinkedAccountsUseCase: RefreshLinkedAccountsUseCase,
private val linkedAccountsUseCase: LinkedAccountsUseCase,
private val refreshBankListUseCase: RefreshBankListUseCase,
private val naviPayCustomerStatusHandler: NaviPayCustomerStatusHandler,
private val naviPayNetworkConnectivity: NaviPayNetworkConnectivity,
savedStateHandle: SavedStateHandle,
private val rewardsNudgeEntityFetchUseCase: RewardsNudgeEntityFetchUseCase
) : NaviPayBaseVM(NaviPayVmData(screenName = NaviPayAnalytics.NAVI_PAY_ADD_ACCOUNT)) {
private val naviPayAnalytics: NaviPayAnalytics.NaviPaySelectBank =
NaviPayAnalytics.INSTANCE.NaviPaySelectBank()
private val MAX_ACCOUNT_SEARCH_RETRY_COUNT = 3
private val isFreshOnBoardingCase = savedStateHandle.get<Boolean>("isFreshOnBoardingCase")!!
private val accountType = savedStateHandle.get<AccountType>("accountType")!!
var linkedAccounts: List<LinkedAccountEntity>? = null
private var noBankFoundCount = 0
private val _searchQuery = MutableStateFlow("")
val searchQuery = _searchQuery.asStateFlow()
private var fetchAccountsJob: Job? = null
var isAddBankSuccessShown = false
private val _upiNumberBottomSheetNeedToShow = MutableStateFlow(false)
val upiNumberBottomSheetNeedToShow = _upiNumberBottomSheetNeedToShow.asStateFlow()
var vpa = ""
var bankName = ""
private val _rewardsNudgeDetailEntity = MutableStateFlow<NudgeDetailEntity?>(null)
val rewardsNudgeDetailEntity = _rewardsNudgeDetailEntity.asStateFlow()
private val _shouldShowSponsor = MutableStateFlow(false)
val shouldShowSponsor = _shouldShowSponsor.asStateFlow()
val isTrailingIconEnabled =
searchQuery
.map { it.isNotEmpty() }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = false
)
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
val allBankListPager: Flow<PagingData<BankEntity>> =
searchQuery
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { query ->
Pager(
config = PagingConfig(pageSize = 20, enablePlaceholders = false),
pagingSourceFactory = {
when (accountType) {
AccountType.CREDIT -> {
bankRepository.getAllCreditCardBanksForQueryString(
queryString = query
)
}
AccountType.SAVINGS -> {
bankRepository.getAllNonCreditCardBanksForQueryString(
queryString = query
)
}
AccountType.UPICREDIT -> {
bankRepository.getAllCreditLineBanksForQueryString(
queryString = query
)
}
}
}
)
.flow
}
.cachedIn(viewModelScope)
private val _popularBanks = MutableStateFlow<List<BankEntity>>(emptyList())
val popularBanks = _popularBanks.asStateFlow()
private val _bottomSheetStateHolder =
MutableStateFlow(
SelectBankScreenBottomSheetHandler(
showBottomSheet = false,
bottomSheetStateChange = true,
bottomSheetUIState = SelectBankScreenBottomSheetUIState.Searching
)
)
val bottomSheetStateHolder = _bottomSheetStateHolder.asStateFlow()
var selectedBank: BankEntity? = null
private var selectedAccount: LinkedAccountEntity? = null
private val _uiState = MutableStateFlow<SelectBankScreenStates>(SelectBankScreenStates.Loader)
val uiState = _uiState.asStateFlow()
private val accountsResponse = mutableListOf<AccountItemResponse>()
private val _navigateToNextScreen = MutableSharedFlow<LinkedAccountEntity?>()
val navigateToNextScreen = _navigateToNextScreen.asSharedFlow()
val customerPhoneNumber = BaseUtils.getPhoneNumber()?.takeLast(PHONE_NUMBER_LENGTH) ?: ""
val helpCtaData = getHelpCtaData(NaviPayScreenType.NAVI_PAY_BANK_SELECTION_BOTTOM_SHEET.name)
private val _navigateToNextScreenFromHelpCta = MutableSharedFlow<CtaData?>()
val navigateToNextScreenFromHelpCta = _navigateToNextScreenFromHelpCta.asSharedFlow()
init {
fetchBankListAndUpdateUIState()
updateRewardsNudgeEntity()
updateSponsorVisibility(isVisible = true)
}
private fun fetchBankListAndUpdateUIState() {
viewModelScope.launch(Dispatchers.IO) {
refreshBankListUseCase.execute()
updatePopularBankList()
updateUIState(state = SelectBankScreenStates.SelectBank)
}
}
private fun updateSponsorVisibility(isVisible: Boolean) {
viewModelScope.launch {
delay(1000) // Added delay to wait for list to finish rendering
_shouldShowSponsor.update { isVisible }
}
}
private fun updateRewardsNudgeEntity() {
viewModelScope.launch(Dispatchers.IO) {
_rewardsNudgeDetailEntity.update { rewardsNudgeEntityFetchUseCase.execute() }
}
}
private suspend fun updatePopularBankList() {
_popularBanks.update { bankRepository.getAllPopularBanks() }
}
fun updateSearchQueryStringState(searchQuery: String = "") {
_searchQuery.update { searchQuery }
}
fun updateBottomSheetUIState(
selectBankScreenBottomSheetUIState: SelectBankScreenBottomSheetUIState? = null,
allowStateChange: Boolean? = null,
showBottomSheet: Boolean
) {
_bottomSheetStateHolder.update {
SelectBankScreenBottomSheetHandler(
showBottomSheet = showBottomSheet,
bottomSheetStateChange = allowStateChange ?: it.bottomSheetStateChange,
bottomSheetUIState = selectBankScreenBottomSheetUIState ?: it.bottomSheetUIState
)
}
}
private fun updateSelectedAccount(selectedAccount: LinkedAccountEntity?) {
this.selectedAccount = selectedAccount
}
private fun updateSelectedBank(selectedBank: BankEntity?) {
this.selectedBank = selectedBank
}
private suspend fun fetchBankAccounts(bankEntity: BankEntity) {
if (!naviPayNetworkConnectivity.isInternetConnected()) {
notifyError(getNoInternetErrorConfig())
return
}
if (naviPayNetworkConnectivity.isAirplaneModeOn()) {
notifyError(getAirplaneModeOnErrorConfig())
return
}
val currentSimInfoList = naviPayNetworkConnectivity.getCurrentSimInfoList()
val simInfoValidationResult =
NaviPayCommonUtils.validateSimInfo(
currentSimInfoList = currentSimInfoList,
deviceInfoProvider = deviceInfoProvider
)
if (!simInfoValidationResult) {
notifyError(getSimFailureErrorConfig(isNoSimPresent = currentSimInfoList.isEmpty()))
return
}
updateBottomSheetUIState(
selectBankScreenBottomSheetUIState = SelectBankScreenBottomSheetUIState.Searching,
allowStateChange = false,
showBottomSheet = true
)
val fetchBankAccountAPIResponse =
bankRepository.fetchBankAccounts(
fetchAccountsRequest =
FetchAccountsRequest(
bankCode = bankEntity.code,
deviceData = deviceInfoProvider.getDeviceData(),
merchantCustomerId = deviceInfoProvider.getMerchantCustomerId(),
accountType =
if (
AccountType.isAccountOfTypeCreditCardOrCreditLine(
type = accountType.name
)
)
accountType.name
else null
)
)
if (!fetchBankAccountAPIResponse.isSuccessWithData()) {
updateBottomSheetUIState(showBottomSheet = false)
notifyError(response = fetchBankAccountAPIResponse, tag = "onBankAccountsFailure")
return
}
val fetchBankAccountsList = fetchBankAccountAPIResponse.data!!.accounts
if (fetchBankAccountsList.isEmpty()) {
noBankFoundCount++
if (noBankFoundCount == MAX_ACCOUNT_SEARCH_RETRY_COUNT && isFreshOnBoardingCase) {
sharedPreferenceRepository.clearKeyBasedSessionPreferenceData(
encryptedDataKeys = NAVI_PAY_ENCRYPT_SHARED_PREF_DATA_KEYS,
nonEncryptedDataKeys = NAVI_PAY_NON_ENCRYPT_SHARED_PREF_DATA_KEYS
)
updateBottomSheetUIState(
selectBankScreenBottomSheetUIState = SelectBankScreenBottomSheetUIState.ReBind,
allowStateChange = false,
showBottomSheet = true
)
} else {
updateBottomSheetUIState(
selectBankScreenBottomSheetUIState =
SelectBankScreenBottomSheetUIState.NotFound,
allowStateChange = true,
showBottomSheet = true
)
}
} else {
noBankFoundCount = 0
accountsResponse.refresh(fetchBankAccountsList)
val accountEntities =
accountsResponse.map { it.toLinkedAccountEntity(selectedBank = selectedBank) }
updateSelectedAccount(selectedAccount = accountEntities[0])
if (accountEntities.size == 1) {
linkAccount()
} else {
updateBottomSheetUIState(
selectBankScreenBottomSheetUIState =
SelectBankScreenBottomSheetUIState.Found(accountEntities),
allowStateChange = true,
showBottomSheet = true
)
}
}
}
private suspend fun getLinkedAccount(bankAccountUniqueId: String): LinkedAccountEntity? {
linkedAccounts = linkedAccountsUseCase.execute().first()
return linkedAccounts?.singleOrNull { it.accountId == bankAccountUniqueId }
}
fun onBankSelected(bankEntity: BankEntity) {
if (fetchAccountsJob?.isActive == true) {
naviPayAnalytics.onBankClickFetchBankRunning(bank = bankEntity)
return
}
fetchAccountsJob =
viewModelScope.launch(Dispatchers.IO) {
updateSelectedBank(selectedBank = bankEntity)
updateSelectedAccount(selectedAccount = null)
fetchBankAccounts(bankEntity = bankEntity)
}
}
fun onAccountSelected(bankAccountEntity: LinkedAccountEntity) {
selectedAccount = bankAccountEntity
}
fun linkAccount() {
viewModelScope.launch(Dispatchers.IO) {
val selectedAccount =
accountsResponse.firstOrNull {
it.bankAccountUniqueId == selectedAccount?.accountId
} ?: return@launch
val selectedVpa =
if (selectedAccount.vpaSuggestions.isNotEmpty()) selectedAccount.vpaSuggestions[0]
else ""
updateBottomSheetUIState(showBottomSheet = false)
updateUIState(state = SelectBankScreenStates.AddBankInProgress)
val addBankAPIResponse =
bankRepository.addBankAccount(
addAccountRequest =
AddAccountRequest(
deviceData = deviceInfoProvider.getDeviceData(),
bankAccountUniqueId = selectedAccount.bankAccountUniqueId,
accountType = accountType.name,
customerVpa = selectedVpa,
merchantCustomerId = deviceInfoProvider.getMerchantCustomerId(),
creditLineAccSubType = selectedAccount.creditLineAccSubType,
creditLineAllowedMCC = selectedAccount.creditLineAllowedMCC,
creditLineNotAllowedMCC = selectedAccount.creditLineNotAllowedMCC
)
)
if (addBankAPIResponse.isSuccess()) {
naviPayCustomerStatusHandler.updateCustomerStatus(
NaviPayCustomerStatus.LINKED_VPA.name
)
naviPayAnalytics.onSelectedBankAccountSelected(selectedAccount = selectedAccount)
refreshLinkedAccountsUseCase.execute()
updateUIState(
state =
SelectBankScreenStates.AddBankSuccess(
connectedAccount =
getLinkedAccount(
bankAccountUniqueId = selectedAccount.bankAccountUniqueId
)
)
)
if (
!isFreshOnBoardingCase &&
!AccountType.isAccountOfTypeCreditCardOrCreditLine(type = accountType.name)
) {
fetchUpiNumberLinkedAccounts()
}
} else {
updateSearchQueryStringState() // resetting search query
updateUIState(state = SelectBankScreenStates.SelectBank)
val error = getError(response = addBankAPIResponse)
updateBottomSheetUIState(
selectBankScreenBottomSheetUIState =
SelectBankScreenBottomSheetUIState.AddAccountError(
title = error.title,
description = error.description
),
allowStateChange = true,
showBottomSheet = true
)
}
}
}
private suspend fun fetchUpiNumberLinkedAccounts() {
if (FirebaseRemoteConfigHelper.getBoolean(FirebaseRemoteConfigHelper.UPI_NUMBER_DISABLED))
return
val response =
bankRepository.fetchUpiNumbers(
UpiNumbersListRequest(
deviceData = deviceInfoProvider.getDeviceData(),
merchantCustomerId = deviceInfoProvider.getMerchantCustomerId()
)
)
if (response.isSuccessWithData()) {
val phoneNumber = deviceInfoProvider.getPhoneNumber().takeLast(PHONE_NUMBER_LENGTH)
var upiNumberLinked = false
var alternativeVpa = ""
var alternativeBankName = ""
response.data?.accounts?.forEach { upiNumberAccountResponseItem ->
upiNumberAccountResponseItem.customerVpas?.forEach {
upiNumberCustomerVpaResponseItem ->
if (
upiNumberAccountResponseItem.isAccountPrimary &&
upiNumberCustomerVpaResponseItem.primary
) {
vpa = upiNumberCustomerVpaResponseItem.vpa
bankName =
getBankNameAccountNumberText(
bankName = upiNumberAccountResponseItem.bankName,
maskedAccountNumber =
upiNumberAccountResponseItem.maskedAccountNumber
)
}
alternativeVpa = upiNumberCustomerVpaResponseItem.vpa
alternativeBankName =
getBankNameAccountNumberText(
bankName = upiNumberAccountResponseItem.bankName,
maskedAccountNumber = upiNumberAccountResponseItem.maskedAccountNumber
)
upiNumberCustomerVpaResponseItem.upiNumbers?.forEach { upiNumberResponseItem ->
if (
upiNumberResponseItem.status == UPI_NUMBER_STATUS_ACTIVE &&
upiNumberResponseItem.number == phoneNumber
) {
upiNumberLinked = true
}
}
}
}
if (upiNumberLinked.not()) {
if (vpa.isEmpty()) {
vpa = alternativeVpa
bankName = alternativeBankName
}
_upiNumberBottomSheetNeedToShow.update { true }
}
}
}
private suspend fun updateNavigateToNextScreen(connectedAccount: LinkedAccountEntity?) {
_navigateToNextScreen.emit(value = connectedAccount)
}
fun onAddAccountSuccess(connectedAccount: LinkedAccountEntity?) {
viewModelScope.launch(Dispatchers.IO) {
if (isFreshOnBoardingCase) {
delay(2000)
}
updateNavigateToNextScreen(connectedAccount = connectedAccount)
isAddBankSuccessShown = true
}
}
fun updateUIState(state: SelectBankScreenStates) {
_uiState.update { state }
}
private suspend fun updateNavigateToNextScreenOnHelpCtaClicked(ctaData: CtaData?) {
_navigateToNextScreenFromHelpCta.emit(ctaData)
}
fun onHelpClicked() {
viewModelScope.launch(Dispatchers.Default) {
updateNavigateToNextScreenOnHelpCtaClicked(helpCtaData)
}
}
}
sealed class SelectBankScreenStates {
data object Loader : SelectBankScreenStates()
data object SelectBank : SelectBankScreenStates()
data object AddBankInProgress : SelectBankScreenStates()
data class AddBankSuccess(val connectedAccount: LinkedAccountEntity?) :
SelectBankScreenStates()
}
sealed class SelectBankScreenBottomSheetUIState {
data object Searching : SelectBankScreenBottomSheetUIState()
data class Found(val accountEntities: List<LinkedAccountEntity>) :
SelectBankScreenBottomSheetUIState()
data object NotFound : SelectBankScreenBottomSheetUIState()
data object ReBind : SelectBankScreenBottomSheetUIState()
data class AddAccountError(val title: String, val description: String) :
SelectBankScreenBottomSheetUIState()
data class NpciRecommendation(val connectedAccount: LinkedAccountEntity?) :
SelectBankScreenBottomSheetUIState()
data object Default : SelectBankScreenBottomSheetUIState()
}

View File

@@ -39,7 +39,6 @@ import com.navi.pay.common.viewmodel.NaviPayBaseVM
import com.navi.pay.destinations.LinkedAccountBalanceScreenDestination
import com.navi.pay.destinations.LinkedAccountDetailScreenDestination
import com.navi.pay.destinations.LinkedAccountVerifyScreenDestination
import com.navi.pay.destinations.SelectBankScreenDestination
import com.navi.pay.destinations.SendMoneyScreenDestination
import com.navi.pay.destinations.SendMoneyScreenV2Destination
import com.navi.pay.destinations.SetPinConfirmationScreenDestination
@@ -63,9 +62,7 @@ import com.navi.pay.onboarding.account.linked.model.view.LinkedAccountsScreenSou
import com.navi.pay.onboarding.account.linked.model.view.LinkedAccountsScreenSource.Companion.getLinkedAccountsScreenName
import com.navi.pay.onboardingV2.model.view.NaviPayOnboardingActionsV2Type
import com.navi.pay.onboardingV2.model.view.OnboardingIntentData
import com.navi.pay.utils.ACCOUNT_TYPE
import com.navi.pay.utils.ACTION_PIN_SET
import com.navi.pay.utils.ADD_ACCOUNT_LINK
import com.navi.pay.utils.AVAILABILITY_ACTION_CHECK
import com.navi.pay.utils.AVAILABILITY_ACTION_PORT
import com.navi.pay.utils.BANK_NAME
@@ -790,19 +787,6 @@ constructor(
allowStateChange = true
)
}
ADD_ACCOUNT_LINK -> {
viewModelScope.launch {
val accountType =
AccountType.getAccountType(data.getStringExtra(ACCOUNT_TYPE).orEmpty())
_navigateToNextScreen.emit(
value =
SelectBankScreenDestination(
isFreshOnBoardingCase = false,
accountType = accountType
)
)
}
}
}
data.removeExtra(DEEPLINK_URL)
}

View File

@@ -68,7 +68,6 @@ import com.navi.payment.nativepayment.components.MPSLoadingShimmer
import com.navi.payment.nativepayment.components.MPSNonOnboardedView
import com.navi.payment.nativepayment.components.MPSOnboardedView
import com.navi.payment.nativepayment.dataprovider.getMpinSetAction
import com.navi.payment.nativepayment.dataprovider.getUpiOnboardingAction
import com.navi.payment.nativepayment.screens.destinations.TransactionPollingScreenDestination
import com.navi.payment.nativepayment.utils.MPSScreenUtils
import com.navi.payment.nativepayment.utils.toGenericError
@@ -92,7 +91,6 @@ import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch
import org.json.JSONException
import org.json.JSONObject
@Destination
@Composable
@@ -354,9 +352,11 @@ fun MPSScreen(
val onPayButtonClick: () -> Unit = {
try {
var payButtonClickEventName = "navi_pmt_mps_pay_now"
var entryPoint = UpiIntent.SEND_MONEY.name
if (mpsViewModel.shouldTriggerDelayedOnboarding()) {
if (mpsViewModel.isUserOnboarded().not()) {
paymentViewModel.sendEvent(
"navi_pmt_pay_via_navi_upi",
mapOf("is_discount_applied" to isDiscountApplied.toString())
)
mpsViewModel.updateBottomSheetUIState(false)
naviPaymentActivity.launchOnboardingSDK(
action = NaviPayOnboardingActionsV2Type.E2E_ONBOARDING.name,
@@ -364,34 +364,24 @@ fun MPSScreen(
launcher = upiResultLauncher,
source = "MPS"
)
} else {
val data: JSONObject? =
if (mpsViewModel.isNaviUpiOnboarded.value.not()) {
payButtonClickEventName = "navi_pmt_pay_via_navi_upi"
entryPoint = UpiIntent.ONBOARDING.name
getUpiOnboardingAction()
} else if (selectedBankAccount?.isMPinSet.orFalse().not()) {
payButtonClickEventName = "navi_pmt_set_upi_pin_btn_click"
entryPoint = UpiIntent.SET_PIN.name
getMpinSetAction(selectedBankAccount?.accountId.orEmpty())
} else {
null
}
} else if (selectedBankAccount?.isMPinSet.orFalse().not()) {
paymentViewModel.sendEvent(
payButtonClickEventName,
"navi_pmt_set_upi_pin_btn_click",
mapOf("is_discount_applied" to isDiscountApplied.toString())
)
if (data.isNotNull()) {
paymentViewModel.sendEvent(
"navi_pmt_redirection_to_upi",
mapOf("entry_point" to entryPoint)
)
mpsViewModel.updateBottomSheetUIState(mpsViewModel.isNaviUpiOnboarded.value)
mpsViewModel.startAction(naviPaymentActivity, upiResultLauncher, data!!)
} else {
mpsViewModel.startPayAmount()
}
val data = getMpinSetAction(selectedBankAccount?.accountId.orEmpty())
paymentViewModel.sendEvent(
"navi_pmt_redirection_to_upi",
mapOf("entry_point" to UpiIntent.SET_PIN.name)
)
mpsViewModel.updateBottomSheetUIState(mpsViewModel.isNaviUpiOnboarded.value)
mpsViewModel.startAction(naviPaymentActivity, upiResultLauncher, data)
} else {
paymentViewModel.sendEvent(
"navi_pmt_mps_pay_now",
mapOf("is_discount_applied" to isDiscountApplied.toString())
)
mpsViewModel.startPayAmount()
}
} catch (exception: JSONException) {
exception.log()

View File

@@ -550,8 +550,8 @@ constructor(
}
}
fun shouldTriggerDelayedOnboarding(): Boolean {
return naviPayManager.isUserOnboarded().not()
fun isUserOnboarded(): Boolean {
return naviPayManager.isUserOnboarded()
}
fun onDiscountClicked(isDiscountApplied: Boolean) {