diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/NaviBbpsExt.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/NaviBbpsExt.kt index dc7c124b38..1cb2de5371 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/NaviBbpsExt.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/NaviBbpsExt.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.platform.SoftwareKeyboardController import androidx.core.content.getSystemService import com.navi.base.utils.orFalse import com.navi.bbps.common.CATEGORY_ID_CREDIT_CARD +import com.navi.bbps.common.CATEGORY_ID_MOBILE_PREPAID import com.navi.bbps.common.theme.NaviBbpsColor import com.navi.bbps.feature.mybills.model.view.MyBillEntity import com.navi.common.utils.ClickDebounce @@ -104,6 +105,11 @@ fun MyBillEntity.isRedirectToCustomerInputRequired(): Boolean { return this.categoryId == CATEGORY_ID_CREDIT_CARD && !isConsentProvided() } +fun MyBillEntity.isPrepaidRechargePaidOrWithoutPlanDetails(): Boolean { + return categoryId == CATEGORY_ID_MOBILE_PREPAID && + (isBillPaid || unpaidBillDetails?.planItemDetails.isNullOrEmpty()) +} + fun getBillTitleFromAccountHolderNameOrPrimaryCustomerParams( accountHolderName: String?, primaryCustomerParams: String, diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/repository/BbpsCommonRepository.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/repository/BbpsCommonRepository.kt index 6b7262bd2c..3e548f6dc3 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/repository/BbpsCommonRepository.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/repository/BbpsCommonRepository.kt @@ -172,6 +172,9 @@ constructor( suspend fun fetchSavedBillsByCategory(category: String) = myBillsDao.getBillsByCategory(categoryId = category) + fun fetchSavedBillsByCategoryAsFlow(categoryId: String) = + myBillsDao.getBillsByCategoryAsFlow(categoryId = categoryId) + suspend fun getRewardDetailsV2( metricInfo: MetricInfo> ): RepoResult { diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/theme/NaviBbpsColor.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/theme/NaviBbpsColor.kt index 4ded6da6e4..b4d809e530 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/theme/NaviBbpsColor.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/theme/NaviBbpsColor.kt @@ -70,4 +70,5 @@ object NaviBbpsColor { val lightGreen = Color(0xFFF4FFF8) val bgDarkPurple = Color(0xFF22223D) val bottomSheetScrimColor = bgDarkPurple.copy(alpha = 0.64f) + val secondaryCtaDisabledTextColor = Color(0xFFAAAAAA) } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/ui/NaviBbpsButtons.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/ui/NaviBbpsButtons.kt index 6c16d41f7c..325c16681f 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/ui/NaviBbpsButtons.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/common/ui/NaviBbpsButtons.kt @@ -86,6 +86,7 @@ fun ThemeRoundedButton( fun SecondaryRoundedButton( modifier: Modifier = Modifier, text: String, + enabled: Boolean = true, cornerRadius: Dp = 4.dp, paddingValues: PaddingValues = PaddingValues(vertical = 14.dp, horizontal = 10.dp), onClick: () -> Unit, @@ -99,13 +100,19 @@ fun SecondaryRoundedButton( shape = RoundedCornerShape(cornerRadius), colors = ButtonDefaults.buttonColors(backgroundColor = NaviBbpsColor.ctaSecondary), contentPadding = paddingValues, + enabled = enabled, ) { NaviText( text = text, fontSize = 14.sp, fontFamily = naviFontFamily, fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), - color = NaviBbpsColor.ctaPrimary, + color = + if (enabled) { + NaviBbpsColor.ctaPrimary + } else { + NaviBbpsColor.secondaryCtaDisabledTextColor + }, ) } } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerListViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerListViewModel.kt index 19650f889d..f0938610fd 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerListViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billerlist/BillerListViewModel.kt @@ -206,6 +206,7 @@ constructor( initConfig() initSearchFlow() updateRewardsNudgeEntity() + observeRecentBills() initLocationEvents() } @@ -604,6 +605,32 @@ constructor( ) } + private fun observeRecentBills() { + viewModelScope.launch(dispatcherProvider.io) { + bbpsCommonRepository + .fetchSavedBillsByCategoryAsFlow(categoryId = billCategoryEntity.categoryId) + .distinctUntilChanged() + .collectLatest { recentBills -> + recentBillsEntity = + RecentBillsEntity( + title = resourceProvider.getString(R.string.bbps_recents), + bills = recentBills, + ) + if (billerListState.value is BillerListState.Loaded) { + updateBillerListState( + BillerListState.Loaded( + recentBills = recentBillsEntity, + billerGroups = + (billerListState.value as BillerListState.Loaded).billerGroups, + popularBillers = + (billerListState.value as BillerListState.Loaded).popularBillers, + ) + ) + } + } + } + } + private fun createLoadedStateFromBillerResponse( billerListResponse: RepoResult ): BillerListState.Loaded { diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/BillHistoryDetailsViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/BillHistoryDetailsViewModel.kt index df9a414585..fcbc2ce4d6 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/BillHistoryDetailsViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/BillHistoryDetailsViewModel.kt @@ -12,23 +12,29 @@ import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.FirebaseCrashlytics import com.navi.base.model.CtaData import com.navi.base.utils.DateUtils.getDateTimeObjectFromEpoch +import com.navi.base.utils.EMPTY import com.navi.base.utils.NaviDateFormatter import com.navi.base.utils.NaviNetworkConnectivity import com.navi.base.utils.ResourceProvider +import com.navi.base.utils.SPACE +import com.navi.base.utils.retry import com.navi.bbps.R import com.navi.bbps.common.CATEGORY_ID_MOBILE_PREPAID import com.navi.bbps.common.DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR import com.navi.bbps.common.DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR_COMMA_TIME import com.navi.bbps.common.DATE_TIME_FORMAT_DATE_MONTH_NAME_YEAR_INPUT +import com.navi.bbps.common.DEFAULT_RETRY_COUNT import com.navi.bbps.common.DISPLAYABLE_MOBILE_NUMBER_KEY import com.navi.bbps.common.NaviBbpsAnalytics import com.navi.bbps.common.NaviBbpsScreen +import com.navi.bbps.common.RETRY_INTERVAL_IN_SECONDS import com.navi.bbps.common.model.NaviBbpsVmData import com.navi.bbps.common.model.config.NaviBbpsDefaultConfig import com.navi.bbps.common.repository.BbpsCommonRepository import com.navi.bbps.common.usecase.NaviBbpsConfigUseCase import com.navi.bbps.common.utils.NaviBbpsCommonUtils import com.navi.bbps.common.utils.NaviBbpsCommonUtils.getBbpsMetricInfo +import com.navi.bbps.common.utils.NaviBbpsCommonUtils.isRechargeCategory import com.navi.bbps.common.utils.NaviBbpsDateUtils import com.navi.bbps.common.utils.getDefaultConfig import com.navi.bbps.common.utils.getDisplayableAmount @@ -36,6 +42,7 @@ import com.navi.bbps.common.utils.toBillerAdditionalParamsEntity import com.navi.bbps.common.viewmodel.NaviBbpsBaseVM import com.navi.bbps.feature.billerlist.model.view.BillerItemEntity import com.navi.bbps.feature.billhistorydetail.model.network.TransactionItemResponse +import com.navi.bbps.feature.billhistorydetail.model.view.AmountInfoData import com.navi.bbps.feature.billhistorydetail.model.view.BillTransactionItemEntity import com.navi.bbps.feature.billhistorydetail.model.view.BillTransactionsState import com.navi.bbps.feature.billhistorydetail.model.view.MyBillDetailsBottomSheetType @@ -54,23 +61,34 @@ import com.navi.bbps.feature.destinations.BbpsTransactionDetailsScreenDestinatio import com.navi.bbps.feature.destinations.CustomerDataInputScreenDestination import com.navi.bbps.feature.destinations.PayBillScreenDestination import com.navi.bbps.feature.destinations.PrepaidRechargeScreenDestination +import com.navi.bbps.feature.mybills.MyBillsRepository +import com.navi.bbps.feature.mybills.MyBillsSyncJob +import com.navi.bbps.feature.mybills.MyBillsViewModel.SnackBarState import com.navi.bbps.feature.mybills.model.view.MyBillEntity import com.navi.bbps.feature.paybill.model.view.PayBillSource +import com.navi.bbps.feature.prepaidrecharge.model.view.OperatorItemEntity +import com.navi.bbps.feature.prepaidrecharge.model.view.PlanItemEntity +import com.navi.bbps.isPrepaidRechargePaidOrWithoutPlanDetails import com.navi.bbps.isRedirectToCustomerInputRequired import com.navi.common.di.CoroutineDispatcherProvider +import com.navi.common.extensions.or import com.navi.common.network.models.RepoResult import com.navi.common.network.models.isSuccessWithData +import com.navi.common.upi.AMOUNT import com.navi.pay.R as NaviPayR import com.ramcosta.composedestinations.spec.Direction import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay 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.collectLatest import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update @@ -91,12 +109,17 @@ constructor( private val stringResouceProvider: ResourceProvider, private val naviBbpsConfigUseCase: NaviBbpsConfigUseCase, private val resourceProvider: ResourceProvider, + private val myBillsRepository: MyBillsRepository, + private val myBillsSyncJob: MyBillsSyncJob, ) : NaviBbpsBaseVM( naviBbpsVmData = NaviBbpsVmData(screen = NaviBbpsScreen.NAVI_BBPS_MY_BILL_HISTORY_DETAILS) ) { - private val myBillEntity = savedStateHandle.get("myBillEntity")!! + private val _myBillEntity = + MutableStateFlow(savedStateHandle.get("myBillEntity")!!) + + val myBillEntity = _myBillEntity.asStateFlow() private val source = savedStateHandle.get("source").orEmpty() @@ -124,6 +147,9 @@ constructor( private val _isLoading = MutableStateFlow(true) val isLoading = _isLoading.asStateFlow() + private val _snackBarState = MutableStateFlow(SnackBarState(show = false)) + val snackBarState = _snackBarState.asStateFlow() + private var naviBbpsDefaultConfig = NaviBbpsDefaultConfig() private val naviBbpsAnalytics: NaviBbpsAnalytics.MyBillHistoryDetails = @@ -165,22 +191,37 @@ constructor( naviBbpsConfigUseCase.getDefaultConfig(naviBbpsVmData.screen.screenName) } } + updateSavedBillAsFlow() + } + + private fun updateSavedBillAsFlow() { + viewModelScope.launch(dispatcherProvider.io) { + myBillsRepository + .fetchSavedBillsByCategoryAsFlow(myBillEntity.value.categoryId) + .distinctUntilChanged() + .collectLatest { + val savedBill = it.find { it.billId == myBillEntity.value.billId } + savedBill?.let { _myBillEntity.update { savedBill } } + } + } } fun getDisplayableCustomerParams(): Map { - return if (myBillEntity.categoryId.equals(CATEGORY_ID_MOBILE_PREPAID, ignoreCase = true)) { - mapOf(DISPLAYABLE_MOBILE_NUMBER_KEY to myBillEntity.primaryCustomerParamValue) + return if ( + myBillEntity.value.categoryId.equals(CATEGORY_ID_MOBILE_PREPAID, ignoreCase = true) + ) { + mapOf(DISPLAYABLE_MOBILE_NUMBER_KEY to myBillEntity.value.primaryCustomerParamValue) } else { - myBillEntity.customerParams + myBillEntity.value.customerParams } } fun fetchBillHistoryList() { viewModelScope.launch(dispatcherProvider.io) { - if (myBillEntity.billId.isNullOrEmpty()) { + if (myBillEntity.value.billId.isNullOrEmpty()) { _billHistoryDetailsState.update { BillTransactionsState.createEmptyState( - categoryId = myBillEntity.categoryId, + categoryId = myBillEntity.value.categoryId, billHistoryEmptyStateConfig = naviBbpsDefaultConfig.billHistoryEmptyStateConfig, ) @@ -189,7 +230,7 @@ constructor( } val billTransactionsResponse = billHistoryDetailsRepository.fetchBillTransactions( - savedBillId = myBillEntity.billId, + savedBillId = myBillEntity.value.billId, metricInfo = getBbpsMetricInfo(screenName = naviBbpsVmData.screen.screenName), ) if (billTransactionsResponse.isSuccessWithData()) { @@ -197,7 +238,7 @@ constructor( _billHistoryDetailsState.update { if (transactions.isEmpty()) { BillTransactionsState.createEmptyState( - categoryId = myBillEntity.categoryId, + categoryId = myBillEntity.value.categoryId, billHistoryEmptyStateConfig = naviBbpsDefaultConfig.billHistoryEmptyStateConfig, ) @@ -209,7 +250,7 @@ constructor( } contactNameForMobileNumber = getCustomerNameForMobileNumber( - primaryCustomerParamValue = myBillEntity.primaryCustomerParamValue + primaryCustomerParamValue = myBillEntity.value.primaryCustomerParamValue ) } else { _billHistoryDetailsState.update { BillTransactionsState.Error } @@ -258,7 +299,7 @@ constructor( formattedAmount = amount.getDisplayableAmount(), netAmountAfterDiscount = "", planName = planName ?: "", - primaryCustomerParamValue = myBillEntity.primaryCustomerParamValue, + primaryCustomerParamValue = myBillEntity.value.primaryCustomerParamValue, billDate = if (billDate.isNullOrBlank()) "" else @@ -289,13 +330,17 @@ constructor( _myBillDetailsBottomSheetType.update { MyBillDetailsBottomSheetType.MenuOptions } } + fun updateSnackBarState(show: Boolean, messageId: Int = R.string.bbps_copied_to_clipboard) { + _snackBarState.update { SnackBarState(show = show, messageId = messageId) } + } + fun onTransactionItemClicked(billTransactionItemEntity: BillTransactionItemEntity) { viewModelScope.launch(dispatcherProvider.io) { updateNextScreenDestinationState( BbpsTransactionDetailsScreenDestination( billTransactionItemEntity = billTransactionItemEntity, myBillEntity = - myBillEntity.copy(customerParams = getDisplayableCustomerParams()), + myBillEntity.value.copy(customerParams = getDisplayableCustomerParams()), source = NaviBbpsScreen.NAVI_BBPS_MY_BILL_HISTORY_DETAILS.name, initialSource = initialSource, ) @@ -303,6 +348,61 @@ constructor( } } + fun getNextActionFooterCtaText(): String { + val billState = myBillEntity.value + val categoryId = billState.categoryId + + return when { + billState.isBillPaid -> { + if (categoryId == CATEGORY_ID_MOBILE_PREPAID) { + resourceProvider.getString(R.string.bbps_view_all_plans) + } else { + resourceProvider.getString(R.string.bbps_pay_bill_in_advance) + } + } + + billState.nextActionCtaText.isNotBlank() -> { + billState.nextActionCtaText + } + + isRechargeCategory(categoryId) -> { + resourceProvider.getString(R.string.bbps_recharge) + } + + else -> { + resourceProvider.getString(R.string.bbps_pay_bill_in_advance) + } + } + } + + fun getAmountInfoFooterText(): AmountInfoData? { + val isRechargeCategory = isRechargeCategory(categoryId = myBillEntity.value.categoryId) + return when { + isRechargeCategory && !myBillEntity.value.formattedLastPaidAmount.isBlank() -> { + AmountInfoData( + amountPrefixText = + resourceProvider.getString(resId = R.string.bbps_last_recharged, AMOUNT), + amount = myBillEntity.value.formattedLastPaidAmount, + ) + } + !isRechargeCategory && + !myBillEntity.value.unpaidBillDetails?.amount.isNullOrEmpty() -> { + AmountInfoData( + amountPrefixText = + myBillEntity.value.categoryName + + SPACE + + resourceProvider.getString(resId = R.string.bbps_bill_amount), + amount = + myBillEntity.value.unpaidBillDetails + ?.amount + ?.getDisplayableAmount() + .orEmpty(), + ) + } + else -> null + } + } + fun onDeleteBillMenuClicked(deleteBill: () -> Unit) { _myBillDetailsBottomSheetType.update { MyBillDetailsBottomSheetType.Confirmation( @@ -316,6 +416,55 @@ constructor( } } + fun onMarkAsPaidClicked() { + _myBillDetailsBottomSheetType.update { + MyBillDetailsBottomSheetType.Confirmation( + title = resourceProvider.getString(R.string.bbps_bill_mark_as_paid_heading), + description = + resourceProvider.getString(R.string.bbps_bill_mark_as_paid_description), + firstBtnTextResId = R.string.bbps_no, + secondButtonTextResId = R.string.bbps_yes, + onFirstBtnClick = {}, + onSecondBtnClick = { onMarkBillAsPaidConfirmedCtaClicked(myBillEntity.value) }, + ) + } + } + + private fun onMarkBillAsPaidConfirmedCtaClicked(myBillEntity: MyBillEntity) { + viewModelScope.launch(dispatcherProvider.io) { + delay(50) // before this bottom sheet is getting closed, added for smoother + // transition + if (!naviNetworkConnectivity.isInternetConnected()) { + notifyError(getNoInternetErrorConfig()) + return@launch + } + val response = + retry( + retryCount = DEFAULT_RETRY_COUNT, + retryIntervalInSeconds = RETRY_INTERVAL_IN_SECONDS, + execute = { + myBillsRepository.markBillAsPaid( + myBillEntity.billId, + metricInfo = + getBbpsMetricInfo( + screenName = naviBbpsVmData.screen.screenName, + isNae = { false }, + ), + ) + }, + shouldRetry = { !it.isSuccessWithData() }, + ) + + if (response.isSuccessWithData()) { + updateSnackBarState(show = true, messageId = R.string.bbps_bill_marked_as_paid) + + _myBillEntity.update { it.copy(isBillPaid = true) } + myBillsSyncJob.refreshBillsAsync(screenName = naviBbpsVmData.screen.screenName) + billResponse = null + } + } + } + private fun updateNewBillFetchingState(isInProgress: Boolean) { _isFetchingNewBill.update { isInProgress } } @@ -326,8 +475,8 @@ constructor( ) { val billCategoryEntity = BillCategoryEntity( - categoryId = myBillEntity.categoryId, - title = myBillEntity.categoryName, + categoryId = myBillEntity.value.categoryId, + title = myBillEntity.value.categoryName, iconUrl = "", searchBoxPlaceholderText = "", billerListStateHeading = "", @@ -361,39 +510,101 @@ constructor( billPeriod = billDetails.billPeriod.orEmpty(), ) - val payBillSource = - PayBillSource.Others( - billerDetailsEntity = billerDetails, - billDetailsEntity = billDetailsEntity, - customerParams = myBillEntity.customerParams, - billerId = myBillEntity.billerId, - amount = billDetails.amount, - formattedLastPaidDate = myBillEntity.formattedLastPaidDate, - formattedLastPaidAmount = myBillEntity.formattedLastPaidAmount, - isConsentRequired = myBillEntity.isConsentRequired, - ) - val phoneNumberDetail = PhoneContactEntity( - name = myBillEntity.billerName, - phoneNumber = myBillEntity.primaryCustomerParamValue, - normalisedPhoneNumber = myBillEntity.primaryCustomerParamValue, + name = myBillEntity.value.billerName, + phoneNumber = myBillEntity.value.primaryCustomerParamValue, + normalisedPhoneNumber = myBillEntity.value.primaryCustomerParamValue, ) - val source = NaviBbpsScreen.NAVI_BBPS_MY_BILL_HISTORY_DETAILS.name + if (billCategoryEntity.categoryId == CATEGORY_ID_MOBILE_PREPAID) { + val planItemEntity = + PlanItemEntity( + planName = + myBillEntity.value.unpaidBillDetails + ?.planItemDetails + ?.get("planName") + .orEmpty(), + price = + myBillEntity.value.unpaidBillDetails + ?.planItemDetails + ?.get("price") + .orEmpty(), + validity = + myBillEntity.value.unpaidBillDetails + ?.planItemDetails + ?.get("validity") + .orEmpty(), + talkTime = + myBillEntity.value.unpaidBillDetails + ?.planItemDetails + ?.get("talkTime") + .orEmpty(), + packageDescription = + myBillEntity.value.unpaidBillDetails + ?.planItemDetails + ?.get("packageDescription") + .orEmpty(), + ) - val destination = - PayBillScreenDestination( - billCategoryEntity = billCategoryEntity, - payBillScreenSource = payBillSource, - phoneNumberDetail = phoneNumberDetail, - isRootScreen = isRootScreen, - source = source, - initialSource = initialSource, - isBillSyncRequiredInBackground = false, + val operatorItemEntity = + OperatorItemEntity( + operatorLogoUrl = billerDetails.billerLogoUrl, + operatorCode = EMPTY, + operatorName = myBillEntity.value.billerName, + billerId = billerDetails.billerId, + ) + + val payBillSource = + PayBillSource.Prepaid( + planItemEntity = planItemEntity, + billDetailsEntity = billDetailsEntity, + operatorItemEntity = operatorItemEntity, + customerParams = myBillEntity.value.customerParams, + billerId = billerDetails.billerId, + amount = billDetailsEntity.amount.orEmpty(), + formattedLastPaidDate = myBillEntity.value.formattedLastPaidDate, + formattedLastPaidAmount = myBillEntity.value.formattedLastPaidAmount, + ) + + _navigateToNextScreen.emit( + PayBillScreenDestination( + billCategoryEntity = billCategoryEntity, + payBillScreenSource = payBillSource, + phoneNumberDetail = phoneNumberDetail, + isRootScreen = isRootScreen, + source = source, + initialSource = initialSource, + ) ) + } else { + val payBillSource = + PayBillSource.Others( + billerDetailsEntity = billerDetails, + billDetailsEntity = billDetailsEntity, + customerParams = myBillEntity.value.customerParams, + billerId = myBillEntity.value.billerId, + amount = billDetails.amount, + formattedLastPaidDate = myBillEntity.value.formattedLastPaidDate, + formattedLastPaidAmount = myBillEntity.value.formattedLastPaidAmount, + isConsentRequired = myBillEntity.value.isConsentRequired, + ) - _navigateToNextScreen.emit(destination) + val source = NaviBbpsScreen.NAVI_BBPS_MY_BILL_HISTORY_DETAILS.name + + val destination = + PayBillScreenDestination( + billCategoryEntity = billCategoryEntity, + payBillScreenSource = payBillSource, + phoneNumberDetail = phoneNumberDetail, + isRootScreen = isRootScreen, + source = source, + initialSource = initialSource, + isBillSyncRequiredInBackground = false, + ) + + _navigateToNextScreen.emit(destination) + } } private suspend fun updateNextScreenDestinationState(direction: Direction) { @@ -402,19 +613,19 @@ constructor( fun onPayNowClicked() { viewModelScope.launch(Dispatchers.IO) { - if (myBillEntity.categoryId == CATEGORY_ID_MOBILE_PREPAID) { + if (myBillEntity.value.isPrepaidRechargePaidOrWithoutPlanDetails()) { updateNextScreenDestinationState( PrepaidRechargeScreenDestination( phoneNumberDetail = PhoneContactEntity( name = contactNameForMobileNumber, - phoneNumber = myBillEntity.primaryCustomerParamValue, - normalisedPhoneNumber = myBillEntity.primaryCustomerParamValue, + phoneNumber = myBillEntity.value.primaryCustomerParamValue, + normalisedPhoneNumber = myBillEntity.value.primaryCustomerParamValue, ), billCategoryEntity = BillCategoryEntity( - categoryId = myBillEntity.categoryId, - title = myBillEntity.categoryName, + categoryId = myBillEntity.value.categoryId, + title = myBillEntity.value.categoryName, iconUrl = "", searchBoxPlaceholderText = "", billerListStateHeading = "", @@ -424,19 +635,19 @@ constructor( initialSource = initialSource, ) ) - } else if (myBillEntity.isRedirectToCustomerInputRequired()) { + } else if (myBillEntity.value.isRedirectToCustomerInputRequired()) { goToCustomerDataInputScreen() } else { updateNewBillFetchingState(isInProgress = true) val billerDetails = BillerDetailsEntity( - billerId = myBillEntity.billerId, - billerName = myBillEntity.billerName, - billerLogoUrl = myBillEntity.billerLogoUrl, - status = myBillEntity.status, - isAdhoc = myBillEntity.isAdhoc, - fetchOption = myBillEntity.fetchOption, - paymentAmountExactness = myBillEntity.paymentAmountExactness, + billerId = myBillEntity.value.billerId, + billerName = myBillEntity.value.billerName, + billerLogoUrl = myBillEntity.value.billerLogoUrl, + status = myBillEntity.value.status, + isAdhoc = myBillEntity.value.isAdhoc, + fetchOption = myBillEntity.value.fetchOption, + paymentAmountExactness = myBillEntity.value.paymentAmountExactness, ) if (billResponse?.isSuccessWithData() == true) { @@ -458,24 +669,24 @@ constructor( CustomerDataInputScreenDestination( billerItemEntity = BillerItemEntity( - billerId = myBillEntity.billerId, - billerName = myBillEntity.billerName, - billerLogoUrl = myBillEntity.billerLogoUrl, - status = myBillEntity.status, - isAdhoc = myBillEntity.isAdhoc, + billerId = myBillEntity.value.billerId, + billerName = myBillEntity.value.billerName, + billerLogoUrl = myBillEntity.value.billerLogoUrl, + status = myBillEntity.value.status, + isAdhoc = myBillEntity.value.isAdhoc, state = "", region = "", - categoryId = myBillEntity.categoryId, + categoryId = myBillEntity.value.categoryId, isPopular = false, ), customerParamsExtraData = CustomerParamsExtraData( - existingCustomerParams = myBillEntity.customerParams + existingCustomerParams = myBillEntity.value.customerParams ), billCategoryEntity = BillCategoryEntity( - categoryId = myBillEntity.categoryId, - title = myBillEntity.categoryName, + categoryId = myBillEntity.value.categoryId, + title = myBillEntity.value.categoryName, iconUrl = "", searchBoxPlaceholderText = "", billerListStateHeading = "", @@ -490,23 +701,23 @@ constructor( } private suspend fun fetchBillDetailsResponse(): RepoResult { - naviBbpsAnalytics.onLoaded(myBillEntity = myBillEntity) + naviBbpsAnalytics.onLoaded(myBillEntity = myBillEntity.value) val billerDetails = BillerDetailsEntity( - billerId = myBillEntity.billerId, - billerName = myBillEntity.billerName, - billerLogoUrl = myBillEntity.billerLogoUrl, - status = myBillEntity.status, - isAdhoc = myBillEntity.isAdhoc, - fetchOption = myBillEntity.fetchOption, - paymentAmountExactness = myBillEntity.paymentAmountExactness, + billerId = myBillEntity.value.billerId, + billerName = myBillEntity.value.billerName, + billerLogoUrl = myBillEntity.value.billerLogoUrl, + status = myBillEntity.value.status, + isAdhoc = myBillEntity.value.isAdhoc, + fetchOption = myBillEntity.value.fetchOption, + paymentAmountExactness = myBillEntity.value.paymentAmountExactness, ) val billDetailsRequest = BillDetailsRequest( billerId = billerDetails.billerId, - customerParams = myBillEntity.customerParams, + customerParams = myBillEntity.value.customerParams, deviceDetails = DeviceDetails(ip = naviNetworkConnectivity.getIpAddress()), isConsentProvided = false, ) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/model/view/AmountInfoData.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/model/view/AmountInfoData.kt new file mode 100644 index 0000000000..d025ea2200 --- /dev/null +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/model/view/AmountInfoData.kt @@ -0,0 +1,10 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.bbps.feature.billhistorydetail.model.view + +data class AmountInfoData(val amountPrefixText: String, val amount: String) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/ui/MyBillHistoryDetailsScreen.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/ui/MyBillHistoryDetailsScreen.kt index 7d47274487..f996ca162e 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/ui/MyBillHistoryDetailsScreen.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/billhistorydetail/ui/MyBillHistoryDetailsScreen.kt @@ -10,6 +10,8 @@ package com.navi.bbps.feature.billhistorydetail.ui import androidx.activity.compose.BackHandler 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 @@ -19,6 +21,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.material3.Scaffold @@ -30,6 +33,8 @@ import androidx.compose.runtime.rememberCoroutineScope 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.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -41,6 +46,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.navi.base.utils.EMPTY import com.navi.base.utils.SPACE +import com.navi.base.utils.orTrue import com.navi.bbps.R import com.navi.bbps.common.BULLET import com.navi.bbps.common.NaviBbpsAnalytics @@ -51,12 +57,15 @@ import com.navi.bbps.common.ui.BbpsCircleImage import com.navi.bbps.common.ui.DisplayAccountHolderNameAndPrimaryCustomerParamValue import com.navi.bbps.common.ui.NaviBbpsHeader import com.navi.bbps.common.ui.NaviBbpsModalBottomSheetLayout +import com.navi.bbps.common.ui.SecondaryRoundedButton import com.navi.bbps.common.ui.SetStatusBarColor +import com.navi.bbps.common.utils.NaviBbpsCommonUtils.isRechargeCategory +import com.navi.bbps.common.utils.SnackBarPredefinedConfig import com.navi.bbps.entry.NaviBbpsActivity import com.navi.bbps.entry.NaviBbpsRouter import com.navi.bbps.feature.billhistorydetail.BillHistoryDetailsViewModel +import com.navi.bbps.feature.billhistorydetail.model.view.AmountInfoData import com.navi.bbps.feature.billhistorydetail.model.view.BillTransactionItemEntity -import com.navi.bbps.feature.billhistorydetail.model.view.BillTransactionsState import com.navi.bbps.feature.mybills.model.view.MyBillEntity import com.navi.common.R as CommonR import com.navi.common.customview.LoaderRoundedButton @@ -65,8 +74,10 @@ import com.navi.common.navigation.setResultAndNavigateBack import com.navi.design.font.FontWeightEnum import com.navi.design.font.getFontWeight import com.navi.design.font.naviFontFamily +import com.navi.design.snackbar.SuccessSnackBar import com.navi.design.utils.clickableWithNoGesture import com.navi.naviwidgets.extensions.NaviText +import com.navi.payment.nativepayment.components.BottomBarShadow import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.result.ResultBackNavigator @@ -139,6 +150,8 @@ fun MyBillHistoryDetailsScreen( billHistoryDetailsViewModel.myBillDetailsBottomSheetType.collectAsStateWithLifecycle() val isUserActionBlocked by billHistoryDetailsViewModel.isUserActionBlocked.collectAsStateWithLifecycle() + val snackBarState by billHistoryDetailsViewModel.snackBarState.collectAsStateWithLifecycle() + val myBillEntityState by billHistoryDetailsViewModel.myBillEntity.collectAsStateWithLifecycle() val onTransactionItemClicked = { billTransactionItemEntity: BillTransactionItemEntity -> naviBbpsAnalytics.onTransactionItemClicked( @@ -207,12 +220,14 @@ fun MyBillHistoryDetailsScreen( myBillDetailsBottomSheetType = myBillDetailsBottomSheetType, onDeleteBillMenuClicked = { naviBbpsAnalytics.onDeleteMenuClicked( - myBillEntity = myBillEntity, + myBillEntity = myBillEntityState, source = source, initialSource = initialSource, ) billHistoryDetailsViewModel.onDeleteBillMenuClicked { - naviBbpsActivity.navController.setResultAndNavigateBack(myBillEntity.billId) + naviBbpsActivity.navController.setResultAndNavigateBack( + myBillEntityState.billId + ) } openSheet() naviBbpsAnalytics.onBottomSheetLanded( @@ -233,11 +248,11 @@ fun MyBillHistoryDetailsScreen( topBar = { MyBillsHistoryHeader( navigator = navigator, - myBillEntity = myBillEntity, + myBillEntity = myBillEntityState, isMenuButtonEnabled = isUserActionBlocked.not(), onMoreOptionsClicked = { naviBbpsAnalytics.onKebabMenuClicked( - myBillEntity, + myBillEntityState, source = source, initialSource = initialSource, ) @@ -255,58 +270,72 @@ fun MyBillHistoryDetailsScreen( ) }, content = { innerPadding -> - Column( - modifier = - Modifier.fillMaxSize() - .background(color = NaviBbpsColor.bgDefault) - .padding(innerPadding) + Box( + Modifier.padding(innerPadding).fillMaxSize().background(NaviBbpsColor.textWhite) ) { - BillTransactionsListView( - billHistoryDetailsState = billHistoryDetailsState, - myBillEntity = myBillEntity, - onItemClicked = onTransactionItemClicked, - recordScreenRenderTime = { - billHistoryDetailsViewModel.recordScreenRenderTime( - billHistoryDetailsViewModel.naviBbpsVmData.screen.screenName, - ModuleNameV2.BBPS.name, - ) + Column( + modifier = + Modifier.fillMaxWidth().background(color = NaviBbpsColor.bgDefault) + ) { + BillTransactionsListView( + billHistoryDetailsState = billHistoryDetailsState, + myBillEntity = myBillEntityState, + onItemClicked = onTransactionItemClicked, + recordScreenRenderTime = { + billHistoryDetailsViewModel.recordScreenRenderTime( + billHistoryDetailsViewModel.naviBbpsVmData.screen.screenName, + ModuleNameV2.BBPS.name, + ) + }, + recordApiEndTime = { + billHistoryDetailsViewModel.recordApiEndTime( + billHistoryDetailsViewModel.naviBbpsVmData.screen.screenName + ) + }, + getTransactionStatusDisplayText = + billHistoryDetailsViewModel::getTransactionStatusDisplayText, + ) + } + BottomBarShadow(shadowHeight = 32.dp) + } + }, + snackbarHost = { + if (snackBarState.show) { + SuccessSnackBar( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 32.dp), + show = true, + snackBarConfig = + SnackBarPredefinedConfig.successConfig( + title = stringResource(id = snackBarState.messageId), + trailingIconResId = null, + ), + onDismissed = { + billHistoryDetailsViewModel.updateSnackBarState(show = false) }, - recordApiEndTime = { - billHistoryDetailsViewModel.recordApiEndTime( - billHistoryDetailsViewModel.naviBbpsVmData.screen.screenName - ) - }, - getTransactionStatusDisplayText = - billHistoryDetailsViewModel::getTransactionStatusDisplayText, ) } }, bottomBar = { - val nextCtaText = - when (billHistoryDetailsState) { - is BillTransactionsState.Empty -> - (billHistoryDetailsState as BillTransactionsState.Empty) - .emptyStateEntity - .ctaText - else -> myBillEntity.nextActionCtaText - } - LoaderRoundedButton( - text = nextCtaText, - onClick = { - naviBbpsAnalytics.onPayNowClicked( - myBillEntity, - source = source, - initialSource = initialSource, - ) - billHistoryDetailsViewModel.onPayNowClicked() - }, - enabled = !isUserActionBlocked, - showLoader = isUserActionBlocked, - modifier = - Modifier.fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 32.dp) - .height(48.dp), - ) + Column(modifier = Modifier.fillMaxWidth().background(NaviBbpsColor.textWhite)) { + MyBillsHistoryFooter( + myBillEntity = myBillEntityState, + onMarkAsPaidClicked = { + billHistoryDetailsViewModel.onMarkAsPaidClicked() + openSheet() + }, + onPrimaryButtonClicked = { + naviBbpsAnalytics.onPayNowClicked( + myBillEntityState, + source = source, + initialSource = initialSource, + ) + billHistoryDetailsViewModel.onPayNowClicked() + }, + isUserActionBlocked = isUserActionBlocked, + nextCtaText = billHistoryDetailsViewModel.getNextActionFooterCtaText(), + amountInfoData = billHistoryDetailsViewModel.getAmountInfoFooterText(), + ) + } }, ) } @@ -396,3 +425,146 @@ private fun MyBillsHistoryHeader( } } } + +@Composable +private fun MyBillsHistoryFooter( + myBillEntity: MyBillEntity?, + onMarkAsPaidClicked: () -> Unit, + onPrimaryButtonClicked: () -> Unit, + nextCtaText: String, + isUserActionBlocked: Boolean, + amountInfoData: AmountInfoData?, +) { + + Column( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + if (myBillEntity?.isBillPaid.orTrue()) { + if (!isRechargeCategory(categoryId = myBillEntity?.categoryId.orEmpty())) { + NaviText( + text = stringResource(id = R.string.bbps_no_bills_due_description), + fontSize = 12.sp, + lineHeight = 16.sp, + color = NaviBbpsColor.textTertiary, + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.height(16.dp)) + } + + LoaderRoundedButton( + text = nextCtaText, + onClick = onPrimaryButtonClicked, + enabled = !isUserActionBlocked, + showLoader = isUserActionBlocked, + modifier = Modifier.fillMaxWidth().height(48.dp), + ) + } else { + amountInfoData?.let { BillAmountInfo(amountInfoData = amountInfoData) } + + if (!myBillEntity?.unpaidBillWarning.isNullOrEmpty()) { + Row( + modifier = + Modifier.fillMaxWidth() + .background( + color = NaviBbpsColor.bgError, + shape = + RoundedCornerShape( + topStart = 0.dp, + topEnd = 0.dp, + bottomStart = 4.dp, + bottomEnd = 4.dp, + ), + ) + .padding(vertical = 2.dp), + horizontalArrangement = Arrangement.Center, + ) { + NaviText( + text = myBillEntity.unpaidBillWarning, + fontSize = 12.sp, + lineHeight = 18.sp, + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + color = NaviBbpsColor.onSurfaceCritical, + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + SecondaryRoundedButton( + text = stringResource(id = R.string.bbps_mark_as_paid), + onClick = onMarkAsPaidClicked, + modifier = Modifier.weight(1f), + enabled = !isUserActionBlocked, + ) + + Spacer(modifier = Modifier.width(16.dp)) + + LoaderRoundedButton( + text = nextCtaText, + onClick = onPrimaryButtonClicked, + enabled = !isUserActionBlocked, + showLoader = isUserActionBlocked, + modifier = Modifier.weight(1f).height(48.dp), + ) + } + } + } +} + +@Composable +private fun BillAmountInfo(amountInfoData: AmountInfoData) { + Row( + modifier = + Modifier.fillMaxWidth() + .background( + color = NaviBbpsColor.bgAlt, + shape = + RoundedCornerShape( + topStart = 4.dp, + topEnd = 4.dp, + bottomStart = 0.dp, + bottomEnd = 0.dp, + ), + ) + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + NaviText( + text = amountInfoData.amountPrefixText, + fontSize = 14.sp, + lineHeight = 22.sp, + fontFamily = naviFontFamily, + color = NaviBbpsColor.textPrimary, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_REGULAR), + ) + + NaviText( + text = BULLET, + fontSize = 14.sp, + lineHeight = 22.sp, + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), + color = NaviBbpsColor.textPrimary, + modifier = Modifier.padding(horizontal = 4.dp), + ) + + NaviText( + text = stringResource(id = R.string.bbps_rupee_symbol_x, amountInfoData.amount), + fontSize = 14.sp, + lineHeight = 22.sp, + fontFamily = naviFontFamily, + fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD), + color = NaviBbpsColor.textPrimary, + ) + } +} diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoriesViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoriesViewModel.kt index cc3b81792b..a204e21bd0 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoriesViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/category/BillCategoriesViewModel.kt @@ -83,6 +83,7 @@ import com.navi.bbps.feature.mybills.model.view.MyBillEntity import com.navi.bbps.feature.paybill.model.view.PayBillSource import com.navi.bbps.feature.prepaidrecharge.model.view.OperatorItemEntity import com.navi.bbps.feature.prepaidrecharge.model.view.PlanItemEntity +import com.navi.bbps.isPrepaidRechargePaidOrWithoutPlanDetails import com.navi.bbps.isRedirectToCustomerInputRequired import com.navi.bbps.network.di.NaviBbpsGsonBuilder import com.navi.common.constants.ARC_LOCAL_COUNTER_KEY @@ -516,10 +517,7 @@ constructor( myBillEntityToBillerDetailsEntityMapper.mapMyBillEntityToBillerDetailsEntity( myBillEntity = myBillEntity ) - if ( - myBillEntity.unpaidBillDetails?.planItemDetails.isNullOrEmpty() && - myBillEntity.categoryId == CATEGORY_ID_MOBILE_PREPAID - ) { + if (myBillEntity.isPrepaidRechargePaidOrWithoutPlanDetails()) { goToPrepaidRechargeScreen(myBillEntity) } else if (myBillEntity.isRedirectToCustomerInputRequired()) { goToCustomerInputScreen(myBillEntity) diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ContactListViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ContactListViewModel.kt index ed2b93be17..7c51ed5638 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ContactListViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/contactlist/ContactListViewModel.kt @@ -56,6 +56,9 @@ import com.navi.bbps.feature.mybills.MyBillsSyncJob import com.navi.bbps.feature.mybills.model.view.MyBillEntity import com.navi.bbps.feature.paybill.model.network.PaymentAmountExactness import com.navi.bbps.feature.paybill.model.view.PayBillSource +import com.navi.bbps.feature.prepaidrecharge.model.view.OperatorItemEntity +import com.navi.bbps.feature.prepaidrecharge.model.view.PlanItemEntity +import com.navi.bbps.isPrepaidRechargePaidOrWithoutPlanDetails import com.navi.common.di.CoroutineDispatcherProvider import com.navi.common.extensions.removeSpaces import com.navi.common.model.common.NudgeDetailEntity @@ -71,7 +74,9 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -195,6 +200,7 @@ constructor( initConfig() fetchUserPhoneNumber() updateRewardsNudgeEntity() + observeRecentBills() } private fun initConfig() { @@ -491,6 +497,30 @@ constructor( } } + private fun observeRecentBills() { + viewModelScope.launch(dispatcherProvider.io) { + bbpsCommonRepository + .fetchSavedBillsByCategoryAsFlow(categoryId = billCategoryEntity.categoryId) + .distinctUntilChanged() + .collectLatest { recentBills -> + if (contactListUIState.value is ContactListState.Loaded) { + updateContactListUIState( + ContactListState.Loaded( + recentBills = + RecentBillsEntity( + title = resourceProvider.getString(R.string.bbps_recents), + bills = recentBills, + ), + allContactList = + (contactListUIState.value as ContactListState.Loaded) + .allContactList, + ) + ) + } + } + } + } + private suspend fun getRecentBills(): RecentBillsEntity { val savedBills = bbpsCommonRepository.fetchSavedBillsByCategory(category = billCategoryEntity.categoryId) @@ -580,17 +610,6 @@ constructor( billDetails = myBillEntityToBillDetailsResponseMapper.map(myBillEntity) ) - val payBillSource = - PayBillSource.Others( - billDetailsEntity = billDetailsEntity, - billerDetailsEntity = billerDetails, - customerParams = myBillEntity.customerParams, - formattedLastPaidDate = myBillEntity.formattedLastPaidDate, - formattedLastPaidAmount = myBillEntity.formattedLastPaidAmount, - billerId = billerDetails.billerId, - isConsentRequired = null, - ) - val phoneNumber = PhoneContactEntity( name = phoneNumberDetail.name, @@ -600,16 +619,83 @@ constructor( val screenSource = NaviBbpsScreen.NAVI_BBPS_CONTACT_LIST_SCREEN.name - _navigateToNextScreen.emit( - PayBillScreenDestination( - billCategoryEntity = billCategory, - payBillScreenSource = payBillSource, - phoneNumberDetail = phoneNumber, - isRootScreen = isRootScreen, - source = screenSource, - initialSource = initialSource, + if (billCategory.categoryId == CATEGORY_ID_MOBILE_PREPAID && !myBillEntity.isBillPaid) { + val planItemEntity = + PlanItemEntity( + planName = + myBillEntity.unpaidBillDetails?.planItemDetails?.get("planName").orEmpty(), + price = myBillEntity.unpaidBillDetails?.planItemDetails?.get("price").orEmpty(), + validity = + myBillEntity.unpaidBillDetails?.planItemDetails?.get("validity").orEmpty(), + talkTime = + myBillEntity.unpaidBillDetails?.planItemDetails?.get("talkTime").orEmpty(), + packageDescription = + myBillEntity.unpaidBillDetails + ?.planItemDetails + ?.get("packageDescription") + .orEmpty(), + ) + + val operatorItemEntity = + OperatorItemEntity( + operatorLogoUrl = billerDetails.billerLogoUrl, + operatorCode = EMPTY, + operatorName = myBillEntity.billerName, + billerId = billerDetails.billerId, + ) + + val payBillSource = + PayBillSource.Prepaid( + planItemEntity = planItemEntity, + billDetailsEntity = billDetailsEntity!!, + operatorItemEntity = operatorItemEntity, + customerParams = myBillEntity.customerParams, + billerId = billerDetails.billerId, + amount = billDetailsEntity.amount.orEmpty(), + formattedLastPaidDate = myBillEntity.formattedLastPaidDate, + formattedLastPaidAmount = myBillEntity.formattedLastPaidAmount, + ) + + _navigateToNextScreen.emit( + PayBillScreenDestination( + billCategoryEntity = billCategory, + payBillScreenSource = payBillSource, + phoneNumberDetail = phoneNumber, + isRootScreen = isRootScreen, + source = screenSource, + initialSource = initialSource, + ) ) - ) + } else { + val payBillSource = + PayBillSource.Others( + billDetailsEntity = billDetailsEntity, + billerDetailsEntity = billerDetails, + customerParams = myBillEntity.customerParams, + formattedLastPaidDate = myBillEntity.formattedLastPaidDate, + formattedLastPaidAmount = myBillEntity.formattedLastPaidAmount, + billerId = billerDetails.billerId, + isConsentRequired = null, + ) + + val phoneNumber = + PhoneContactEntity( + name = phoneNumberDetail.name, + phoneNumber = phoneNumberDetail.phoneNumber, + normalisedPhoneNumber = phoneNumberDetail.normalisedPhoneNumber, + ) + + _navigateToNextScreen.emit( + PayBillScreenDestination( + billCategoryEntity = billCategory, + payBillScreenSource = payBillSource, + phoneNumberDetail = phoneNumber, + isRootScreen = isRootScreen, + source = screenSource, + initialSource = initialSource, + ) + ) + } } fun onRecentBillItemClicked(phoneNumberDetail: PhoneContactEntity, myBillEntity: MyBillEntity) { @@ -621,7 +707,17 @@ constructor( source = source, initialSource = initialSource, ) - if (billCategoryEntity.categoryId == CATEGORY_ID_MOBILE_PREPAID) { + val billerDetails = + BillerDetailsEntity( + billerId = myBillEntity.billerId, + billerName = myBillEntity.billerName, + billerLogoUrl = myBillEntity.billerLogoUrl, + status = myBillEntity.status, + isAdhoc = myBillEntity.isAdhoc, + fetchOption = EMPTY, + paymentAmountExactness = myBillEntity.paymentAmountExactness, + ) + if (myBillEntity.isPrepaidRechargePaidOrWithoutPlanDetails()) { updateNextScreenDestinationState( PrepaidRechargeScreenDestination( phoneNumberDetail = @@ -645,16 +741,6 @@ constructor( ) ) } else { - val billerDetails = - BillerDetailsEntity( - billerId = myBillEntity.billerId, - billerName = myBillEntity.billerName, - billerLogoUrl = myBillEntity.billerLogoUrl, - status = myBillEntity.status, - isAdhoc = myBillEntity.isAdhoc, - fetchOption = EMPTY, - paymentAmountExactness = myBillEntity.paymentAmountExactness, - ) navigateToNextScreen( billerDetails = billerDetails, phoneNumberDetail = phoneNumberDetail, diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/customerinput/CustomerDataInputViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/customerinput/CustomerDataInputViewModel.kt index c32687372c..94645f6eac 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/customerinput/CustomerDataInputViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/customerinput/CustomerDataInputViewModel.kt @@ -579,6 +579,7 @@ constructor( phoneNumberDetail = phoneNumberDetail, source = source, initialSource = initialSource, + isBillSyncRequiredInBackground = false, ) ) } diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsRepository.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsRepository.kt index 36d4f3b69c..2a65bda352 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsRepository.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsRepository.kt @@ -41,6 +41,9 @@ constructor( suspend fun fetchSavedBillsByCategory(category: String) = myBillsDao.getBillsByCategory(categoryId = category) + fun fetchSavedBillsByCategoryAsFlow(category: String) = + myBillsDao.getBillsByCategoryAsFlow(categoryId = category) + suspend fun markBillAsPaid( billId: String, metricInfo: MetricInfo>, diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsViewModel.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsViewModel.kt index f553b894c8..d058835f75 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsViewModel.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/MyBillsViewModel.kt @@ -72,6 +72,7 @@ import com.navi.bbps.feature.mybills.model.view.MyBillsState import com.navi.bbps.feature.paybill.model.view.PayBillSource import com.navi.bbps.feature.prepaidrecharge.model.view.OperatorItemEntity import com.navi.bbps.feature.prepaidrecharge.model.view.PlanItemEntity +import com.navi.bbps.isPrepaidRechargePaidOrWithoutPlanDetails import com.navi.bbps.isRedirectToCustomerInputRequired import com.navi.common.di.CoroutineDispatcherProvider import com.navi.common.network.models.isSuccessWithData @@ -690,10 +691,7 @@ constructor( myBillEntityToBillerDetailsEntityMapper.mapMyBillEntityToBillerDetailsEntity( myBillEntity = myBillEntity ) - if ( - myBillEntity.unpaidBillDetails?.planItemDetails.isNullOrEmpty() && - myBillEntity.categoryId == CATEGORY_ID_MOBILE_PREPAID - ) { + if (myBillEntity.isPrepaidRechargePaidOrWithoutPlanDetails()) { _navigateToNextScreen.emit( PrepaidRechargeScreenDestination( phoneNumberDetail = diff --git a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/db/MyBillsDao.kt b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/db/MyBillsDao.kt index b6e1a05c9b..2f879b778c 100644 --- a/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/db/MyBillsDao.kt +++ b/android/navi-bbps/src/main/kotlin/com/navi/bbps/feature/mybills/db/MyBillsDao.kt @@ -27,6 +27,9 @@ interface MyBillsDao { @Query("SELECT * FROM $NAVI_BBPS_TABLE_MY_SAVED_BILLS WHERE categoryId == :categoryId") suspend fun getBillsByCategory(categoryId: String): List + @Query("SELECT * FROM $NAVI_BBPS_TABLE_MY_SAVED_BILLS WHERE categoryId == :categoryId") + fun getBillsByCategoryAsFlow(categoryId: String): Flow> + @Update suspend fun updateSavedBill(myBillEntity: MyBillEntity) @Insert(onConflict = OnConflictStrategy.REPLACE) diff --git a/android/navi-bbps/src/main/res/values/strings.xml b/android/navi-bbps/src/main/res/values/strings.xml index 4b1209ac10..c259cc7f9d 100644 --- a/android/navi-bbps/src/main/res/values/strings.xml +++ b/android/navi-bbps/src/main/res/values/strings.xml @@ -357,4 +357,9 @@ Pay again Last payment on %s failed, know why? know why? + You currently have no due bills for this biller. + Pay in advance + Recharge + bill amount + View all plans \ No newline at end of file