TP-66207 | Added fix for mandate screen for create and approve mandate (#10773)

This commit is contained in:
Ujjwal Kumar
2024-05-08 12:46:16 -07:00
committed by GitHub
parent 8ceeb88387
commit 4548444960
4 changed files with 191 additions and 129 deletions

View File

@@ -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) {

View File

@@ -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

View File

@@ -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))

View File

@@ -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<LinkedAccountEntity?>(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<Deferred<Unit>>()
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<LinkedAccountEntity>
): List<LinkedAccountEntity> {
// Account eligibility status update
val updatedLinkedAccountsWithEligibility =
updateAccountEligibilityStatusInLinkedAccounts(linkedAccounts = linkedAccounts)
return updatedLinkedAccountsWithEligibility
}
private suspend fun updateAccountEligibilityStatusInLinkedAccounts(
linkedAccounts: List<LinkedAccountEntity>
): List<LinkedAccountEntity> {
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<LinkedAccountEntity>
) {
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()