From 4548444960ba195ea9502b5f25dd47346ad14066 Mon Sep 17 00:00:00 2001 From: Ujjwal Kumar Date: Wed, 8 May 2024 12:46:16 -0700 Subject: [PATCH] TP-66207 | Added fix for mandate screen for create and approve mandate (#10773) --- .../sendmoney/ui/BankAccountsListSection.kt | 4 +- .../sendmoney/viewmodel/SendMoneyViewModel.kt | 5 - .../MandateDetailScreenOfPendingCategory.kt | 170 +++++++++++------- ...MandateDetailOfPendingCategoryViewModel.kt | 141 +++++++++------ 4 files changed, 191 insertions(+), 129 deletions(-) diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/sendmoney/ui/BankAccountsListSection.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/sendmoney/ui/BankAccountsListSection.kt index 42a2fe2d5d..cbd6f85153 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/sendmoney/ui/BankAccountsListSection.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/sendmoney/ui/BankAccountsListSection.kt @@ -154,8 +154,8 @@ fun BankAccountItem( bankAccountEligibilityState: EligibilityState, onSelected: (LinkedAccountEntity) -> Unit, isSelected: Boolean = false, - isExpandableItem: Boolean = false, // TODO This to be removed - isSeeMoreEnabled: Boolean = true, // TODO This to be removed + isExpandableItem: Boolean = false, // TODO to be removed after mandate UI enhancement + isSeeMoreEnabled: Boolean = true // TODO to be removed after mandate UI enhancement ) { val isItemClickable = if (isExpandableItem) { diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/sendmoney/viewmodel/SendMoneyViewModel.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/sendmoney/viewmodel/SendMoneyViewModel.kt index 8dfbf75bfb..3c8c65552b 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/sendmoney/viewmodel/SendMoneyViewModel.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/management/common/sendmoney/viewmodel/SendMoneyViewModel.kt @@ -1360,11 +1360,6 @@ constructor( initPaymentAmount() - // _bankSelectionEnabled.update { // TODO: check this - // source !is SendMoneyScreenSource.CollectRequest && linkedAccounts.size - // > 1 - // } - checkIfPSPIsDown() // TODO: Move activity intent data to VM & then this entire function to VM init diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/management/mandate/ui/MandateDetailScreenOfPendingCategory.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/management/mandate/ui/MandateDetailScreenOfPendingCategory.kt index c292f6683d..3362d55ec3 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/management/mandate/ui/MandateDetailScreenOfPendingCategory.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/management/mandate/ui/MandateDetailScreenOfPendingCategory.kt @@ -8,7 +8,6 @@ package com.navi.pay.management.mandate.ui import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -17,11 +16,14 @@ 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.Checkbox import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetState import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -66,6 +68,7 @@ import com.navi.pay.destinations.MandateDetailScreenOfPendingCategoryDestination import com.navi.pay.destinations.MandateSummaryScreenDestination import com.navi.pay.entry.NaviPayActivity import com.navi.pay.management.common.sendmoney.model.view.PayeeEntity +import com.navi.pay.management.common.sendmoney.ui.BankAccountItem import com.navi.pay.management.common.sendmoney.ui.BankAccountsListSection import com.navi.pay.management.mandate.model.view.MandateAmountRule import com.navi.pay.management.mandate.model.view.MandateCategory @@ -258,79 +261,89 @@ fun RenderMandateDetailScreenOfPendingCategory( ) } ) { - Column(modifier = Modifier.fillMaxWidth()) { - NaviPayHeader( - title = stringResource(id = R.string.autopay_details), - onNavigationIconClick = { - resultNavigator.navigateBack(result = MandateScreenBackNavigation.NotRefresh) - }, - onActionClick = { - coroutineScope.launch { - mandateDetailOfPendingCategoryViewModel.updateBottomSheetUIState( - bottomSheetUIState = - MandateDetailOfPendingCategoryBottomSheetState.OptionsMenu, - showBottomSheet = true, - allowStateChange = true + Scaffold( + topBar = { + NaviPayHeader( + title = stringResource(id = R.string.autopay_details), + onNavigationIconClick = { + resultNavigator.navigateBack( + result = MandateScreenBackNavigation.NotRefresh ) - } - }, - actionIconId = - if (mandateDetailOfPendingCategoryViewModel.shouldMenuOptionIconBeVisible) - R.drawable.ic_three_dots_option_icon - else -1, - modifier = Modifier.fillMaxWidth() - ) + }, + onActionClick = { + coroutineScope.launch { + mandateDetailOfPendingCategoryViewModel.updateBottomSheetUIState( + bottomSheetUIState = + MandateDetailOfPendingCategoryBottomSheetState.OptionsMenu, + showBottomSheet = true, + allowStateChange = true + ) + } + }, + actionIconId = + if (mandateDetailOfPendingCategoryViewModel.shouldMenuOptionIconBeVisible) + R.drawable.ic_three_dots_option_icon + else -1, + modifier = Modifier.fillMaxWidth() + ) + }, + content = { + Column( + modifier = + Modifier.padding(it).fillMaxWidth().verticalScroll(rememberScrollState()) + ) { + Spacer(modifier = Modifier.height(8.dp)) - Spacer(modifier = Modifier.height(8.dp)) + MandateDetailBasicInfoCard( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + isMandateOfPendingType = true, + mandateEntity = mandateEntity + ) - MandateDetailBasicInfoCard( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - isMandateOfPendingType = true, - mandateEntity = mandateEntity - ) + Spacer(modifier = Modifier.height(24.dp)) - Spacer(modifier = Modifier.height(24.dp)) + MandateDetailPayDateInfoCard( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + mandateEntity = mandateEntity + ) - MandateDetailPayDateInfoCard( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - mandateEntity = mandateEntity - ) + Spacer(modifier = Modifier.height(24.dp)) - Spacer(modifier = Modifier.height(24.dp)) + MandateDetailsStartEndDateInfoCard( + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + showStatus = false, + mandateEntity = mandateEntity + ) - MandateDetailsStartEndDateInfoCard( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - showStatus = false, - mandateEntity = mandateEntity - ) - - Spacer(modifier = Modifier.weight(1f)) - - Box { + Spacer(modifier = Modifier.weight(1f)) + } + }, + bottomBar = { AccountSelectionFooter( mandateDetailOfPendingCategoryViewModel = mandateDetailOfPendingCategoryViewModel, onDeclineButtonClicked = onDeclineButtonClicked, onApproveButtonClicked = onApproveButtonClicked, ) - - SuccessSnackBar( - modifier = - Modifier.align(Alignment.BottomCenter) - .padding(horizontal = 16.dp, vertical = 32.dp), - show = snackBarState, - snackBarConfig = - SnackBarPredefinedConfig.successConfig( - title = stringResource(id = R.string.user_blocked_successfully), - ), - onDismissed = { - mandateDetailOfPendingCategoryViewModel.updateShowSnackBarState( - showSnackBar = false - ) - } - ) + }, + snackbarHost = { + if (snackBarState) { + SuccessSnackBar( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 32.dp), + show = snackBarState, + snackBarConfig = + SnackBarPredefinedConfig.successConfig( + title = stringResource(id = R.string.user_blocked_successfully), + ), + onDismissed = { + mandateDetailOfPendingCategoryViewModel.updateShowSnackBarState( + showSnackBar = false + ) + } + ) + } } - } + ) } } @@ -346,7 +359,7 @@ fun AccountSelectionFooter( val selectedBankAccount by mandateDetailOfPendingCategoryViewModel.selectedBankAccount.collectAsStateWithLifecycle() val showAllBankListView by - mandateDetailOfPendingCategoryViewModel.showAllBankList.collectAsStateWithLifecycle() + mandateDetailOfPendingCategoryViewModel.showAllBankListView.collectAsStateWithLifecycle() val isWarningConsentChecked by mandateDetailOfPendingCategoryViewModel.isWarningConsentChecked .collectAsStateWithLifecycle() @@ -401,16 +414,35 @@ fun AccountSelectionFooter( Spacer(modifier = Modifier.height(16.dp)) - BankAccountsListSection( - modifier = Modifier.fillMaxWidth(), - bankAccountState = bankAccountState, - selectedBankAccount = selectedBankAccount, - onBankAccountSelected = { - mandateDetailOfPendingCategoryViewModel.updateSelectedBankAccount( - selectedBankAccount = it + if (showAllBankListView) { + BankAccountsListSection( + modifier = Modifier.fillMaxWidth(), + bankAccountState = bankAccountState, + selectedBankAccount = selectedBankAccount, + onBankAccountSelected = { + mandateDetailOfPendingCategoryViewModel.updateSelectedBankAccount( + selectedBankAccount = it + ) + } + ) + } else { + selectedBankAccount?.let { + BankAccountItem( + bankAccount = it, + bankAccountEligibilityState = it.eligibilityState, + onSelected = { + if (mandateDetailOfPendingCategoryViewModel.isPendingMandateOfTypeCreate) { + mandateDetailOfPendingCategoryViewModel.updateAllBankListView( + showAllBankList = true + ) + } + }, + isExpandableItem = true, + isSeeMoreEnabled = + mandateDetailOfPendingCategoryViewModel.isPendingMandateOfTypeCreate ) } - ) + } Spacer(modifier = Modifier.height(16.dp)) diff --git a/android/navi-pay/src/main/kotlin/com/navi/pay/management/mandate/viewmodel/MandateDetailOfPendingCategoryViewModel.kt b/android/navi-pay/src/main/kotlin/com/navi/pay/management/mandate/viewmodel/MandateDetailOfPendingCategoryViewModel.kt index a3d83c66ff..6334b26bc0 100644 --- a/android/navi-pay/src/main/kotlin/com/navi/pay/management/mandate/viewmodel/MandateDetailOfPendingCategoryViewModel.kt +++ b/android/navi-pay/src/main/kotlin/com/navi/pay/management/mandate/viewmodel/MandateDetailOfPendingCategoryViewModel.kt @@ -47,7 +47,6 @@ import com.navi.pay.onboarding.account.detail.model.view.LinkedAccountEntity import com.navi.pay.utils.DATE_TIME_FORMAT_DATE_MONTH_YEAR_WITHOUT_SEPARATOR import com.navi.pay.utils.DATE_TIME_FORMAT_YEAR_MONTH_DATE_WITH_SLASH_SEPARATOR import com.navi.pay.utils.REQUEST_TYPE_MANDATE -import com.navi.pay.utils.RESOURCE_DEFAULT_ID import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.Deferred @@ -103,8 +102,8 @@ constructor( private val _selectedBankAccount = MutableStateFlow(null) val selectedBankAccount = _selectedBankAccount.asStateFlow() - private val _showAllBankList = MutableStateFlow(false) - val showAllBankList = _showAllBankList.asStateFlow() + private val _showAllABankList = MutableStateFlow(false) + val showAllBankListView = _showAllABankList.asStateFlow() private val _isWarningConsentChecked = MutableStateFlow(true) val isWarningConsentChecked = _isWarningConsentChecked.asStateFlow() @@ -159,7 +158,7 @@ constructor( initMandateEntity() val asyncJobList = mutableListOf>() - asyncJobList.add(async { fetchLinkedAccountBankList() }) + asyncJobList.add(async { executeLinkedAccountsFetchAndUpdateBankAccountState() }) asyncJobList.add(async { validateVpaAndUpdateIsMerchantStateValues() }) asyncJobList.awaitAll() @@ -171,55 +170,91 @@ constructor( payeeEntity?.let { mandateEntity = it.toMandateEntity() } } - private suspend fun fetchLinkedAccountBankList() { - val linkedAccounts = linkedAccountsUseCase.execute() + private suspend fun executeLinkedAccountsFetchAndUpdateBankAccountState() { + updateBankAccountsState(state = BankAccountsState.Loading) - if (linkedAccounts.isNotEmpty()) { + val linkedAccountsInDB = linkedAccountsUseCase.execute() - val linkedAccountsWithEligibilityStateUpdated = - linkedAccounts.map { linkedAccountEntity -> - val eligibilityState = - if (!linkedAccountEntity.isMPinSet) { - EligibilityState( - isAccountEligible = false, - inEligibilityReasonResId = R.string.mpin_not_set - ) - } else { - val isCreditAccount = - linkedAccountEntity.accountType == AccountType.CREDIT.name - EligibilityState( - isAccountEligible = !isCreditAccount, // CC not allowed for Mandate - inEligibilityReasonResId = - if (isCreditAccount) - R.string.credit_cards_can_only_be_used_for_merchant_payments - else RESOURCE_DEFAULT_ID - ) - } + val postProcessedLinkedAccounts = + postProcessLinkedAccounts(linkedAccounts = linkedAccountsInDB) - linkedAccountEntity.eligibilityState = eligibilityState - linkedAccountEntity - } - - updateBankAccountsState( - state = - BankAccountsState.AccountList( - accounts = linkedAccountsWithEligibilityStateUpdated - ) - ) - - updateSelectedBankAccount( - selectedBankAccount = - linkedAccountsWithEligibilityStateUpdated.singleOrNull { linkedAccountEntity -> - linkedAccountEntity.vpaEntityList.any { vpaInfo -> - vpaInfo.vpa == mandateEntity?.payerVpa - } - } ?: linkedAccountsWithEligibilityStateUpdated[0] - ) - } else { + if (postProcessedLinkedAccounts.isEmpty()) { updateBankAccountsState(state = BankAccountsState.NoAccountLinked) + } else { + updateBankAccountsState( + state = BankAccountsState.AccountList(accounts = postProcessedLinkedAccounts) + ) + + executeAutoSelectionOfBankAccount(linkedAccounts = postProcessedLinkedAccounts) } } + private suspend fun postProcessLinkedAccounts( + linkedAccounts: List + ): List { + + // Account eligibility status update + val updatedLinkedAccountsWithEligibility = + updateAccountEligibilityStatusInLinkedAccounts(linkedAccounts = linkedAccounts) + + return updatedLinkedAccountsWithEligibility + } + + private suspend fun updateAccountEligibilityStatusInLinkedAccounts( + linkedAccounts: List + ): List { + val updatedLinkedAccounts = + linkedAccounts.map { linkedAccount -> + val eligibilityState = + getEligibilityStateForLinkedAccount(linkedAccountEntity = linkedAccount) + linkedAccount.eligibilityState = eligibilityState + linkedAccount + } + + return updatedLinkedAccounts + } + + private suspend fun getEligibilityStateForLinkedAccount( + linkedAccountEntity: LinkedAccountEntity + ): EligibilityState { + + // PIN set check + if (!linkedAccountEntity.isMPinSet) { + return EligibilityState( + isAccountEligible = false, + inEligibilityReasonResId = R.string.mpin_not_set + ) + } + + // Credit account check + if (linkedAccountEntity.accountType == AccountType.CREDIT.name) { + return EligibilityState( + isAccountEligible = false, + inEligibilityReasonResId = + R.string.credit_cards_can_only_be_used_for_merchant_payments + ) + } + + // For create mandate, all accounts are eligible + return if (isPendingMandateOfTypeCreate) { + EligibilityState(isAccountEligible = true) + } else { // For approve mandate, only payer VPA account is eligible + EligibilityState(isAccountEligible = linkedAccountEntity.vpa == mandateEntity?.payerVpa) + } + } + + private suspend fun executeAutoSelectionOfBankAccount( + linkedAccounts: List + ) { + + val defaultSelectedAccount = + linkedAccounts.firstOrNull { linkedAccount -> + linkedAccount.vpa == mandateEntity?.payerVpa + } ?: linkedAccounts.first() + + updateSelectedBankAccount(selectedBankAccount = defaultSelectedAccount) + } + private suspend fun validateVpaAndUpdateIsMerchantStateValues() { val validateVpaAPIResponse = @@ -264,11 +299,11 @@ constructor( fun updateSelectedBankAccount(selectedBankAccount: LinkedAccountEntity) { _selectedBankAccount.update { selectedBankAccount } - updateShowAllBankListState(showAllState = false) + updateAllBankListView(showAllBankList = false) } - fun updateShowAllBankListState(showAllState: Boolean) { - _showAllBankList.update { showAllState } + fun updateAllBankListView(showAllBankList: Boolean) { + _showAllABankList.update { showAllBankList } } fun updateIsChecked(isChecked: Boolean) { @@ -896,9 +931,9 @@ sealed class MandateDetailOfPendingCategoryBottomSheetState { val descriptionId: Int = R.string.loading_bottom_sheet_description ) : MandateDetailOfPendingCategoryBottomSheetState() - object OptionsMenu : MandateDetailOfPendingCategoryBottomSheetState() + data object OptionsMenu : MandateDetailOfPendingCategoryBottomSheetState() - object DeclineRequest : MandateDetailOfPendingCategoryBottomSheetState() + data object DeclineRequest : MandateDetailOfPendingCategoryBottomSheetState() data class Error( val title: String, @@ -910,11 +945,11 @@ sealed class MandateDetailOfPendingCategoryBottomSheetState { data class BlockSpamConfirmation(val reportSpam: Boolean) : MandateDetailOfPendingCategoryBottomSheetState() - object BlockLoader : MandateDetailOfPendingCategoryBottomSheetState() + data object BlockLoader : MandateDetailOfPendingCategoryBottomSheetState() } sealed class NavigateToNextScreenFromMandateDetailOfPendingCategory { - object Back : NavigateToNextScreenFromMandateDetailOfPendingCategory() + data object Back : NavigateToNextScreenFromMandateDetailOfPendingCategory() data class MandateSummary(val mandateSummaryEntity: MandateSummaryEntity) : NavigateToNextScreenFromMandateDetailOfPendingCategory()